Курсы javascript
Добры день. Возможно мой вопрос покажется детским, но меня очень волнует вопрос как создавать многоуровневые списки по средствам JS ?
Как сделать добавление подпунктов списка?
List
У меня вышло так что первый элемент списка переопределился второй строкой, а как создать новую строку. (или вообще как создать список из li [n] элементов?
Я понимаю, что список по своему подобию есть массивом данных, но как сделать ориентацию в этом массиве не пойму.
Как добавить элемент в массив JavaScript по условию?
Учебное пособие по определению массива JavaScript с условно добавляемыми в него элементами.
Иногда мы хотим условно добавить элементы в массив JavaScript. В этой статье мы рассмотрим, как определить массив JavaScript с условно добавленными в него элементами.
Использование оператора распыления (spread, . )
Мы можем использовать оператор spread для распыления массива в другой массив. Например, мы можем написать:
const items = [ 'foo', . true ? ['bar'] : [], . false ? ['falsy'] : [], ] console.log(items)
Тогда items будет ["foo", "bar"] , так как у нас есть тернарное выражение, в котором false является значением условной части тернарного выражения. Если оно истинно, то содержимое массива после ? попадет в items . В противном случае в items будут помещены элементы пустого массива.
Вызов Array.prototype.push по условию
Еще один способ добавить элементы в массив JavaScript по условию - использовать метод push . Например, мы можем написать:
const items = [ 'foo', ] if (true) < items.push("bar"); >if (false) < items.push("false"); >console.log(items)
Тогда 'bar' добавляется к элементам, а 'false' - нет. Это происходит потому, что первое условное значение имеет условие true , а второе - false . Поэтому items снова будет ["foo", "bar"] .
Итоги
Мы можем распылять (spread) элементы в массив по условию с помощью оператора spread. Также мы можем вызывать push для добавления элементов в массив JavaScript по условию if / else .
Как создать массив, заполненный числами в JavaScript
год назад · 1 мин. на чтение
Иногда нужно создать массив заполненный числами в JavaScript или создать массив от 1 до определённого числа. В этой статье содержится короткий ответ для быстрого решения.
Короткое решение
Для получения массива чисел от 0 до N, можно воспользоваться методом массива from .
Array.from(< length: 10 >, (_, i) => i) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Более универсальный способ в виде функции
length - длина массива. fn - функция для кастомизации элемента. Т.к. числа по умолчанию начинаются с нуля, мы можем передать i => i + 1 и получить массив от 1 до N, как в примере ниже.
const getNumbers = (length, fn = i => i) => < return Array.from(< length >, (_, i) => fn(i)) >
console.log(getNumbers(10, i => i + 1)) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] console.log(getNumbers(10)) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Реверсированный массив
Здесь в функцию передается не просто length , а length - 1 . Это делается для того, чтобы сохранить соглашение, что индексы начинаются с 0 . См. пример ниже.
const getNumbersReversed = (length, fn = i => i ) => < return Array.from(< length >, (_, i) => fn(length - 1 - i)) >
console.log(getNumbersReversed(10, i => i + 1)) // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1] console.log(getNumbersReversed(10)) // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
как сделать список в js
Понятие списка в программировании является более общим понятием и может отличаться реализацией и особенностями структуры. В javascript в качестве списка чаще выступает обычный массив.
const products = ['apple', 'orange', 'banana']; // Список продуктов
Пример работы над элементами списка:
// добавляем элемент в конец списка list.push('grape'); // добавляем элемент в начало списка list.unshift('pear'); // удаляем элемент из списка list.splice(2, 1); // удалит "orange" из списка // изменяем элемент списка list[1] = 'kiwi';
Списки
Списки являются ключевой структурой организации данных в задачах искусственного интеллекта. В принципе, массивы JavaScript предоставляют все необходимые функции по работе со списками. Пустой список объявляется так: var lst = [];. Затем элемент it можно добавить в конец: lst.push(it) или начало: lst.unshift(it) списка. Функцией it=lst.pop() элемент выталкивается из конца списка, а it=lst.shift() - из начала. Слова shift и unshift как раз связаны с терминологией обыденных очередей. Когда кто-то залазит в очередь спереди, она вынуждена сдвинуться назад (unshift). При выходе первого человека из очереди - она естественным образом сдвигается вперёд (shift).
Удалить count элементов, начиная с i-того можно вызовом lst.splice(i, count). Эта же функция при вызове lst.splice(i, 0, it) вставляет элемент it на место индекса i (после lst[i-1]). Функция lst.reverse() обращает последовательность элементов списка. Ниже, справа от кода скрипта приведен результат вызова document.write(res); и тег br> в html означает переход к следующей строке:
var lst = [], n, res = 'length = '+lst.length+"
"; lst.push(0); res += lst + '
'; lst.push(1); res += lst + '
'; lst.push(2); res += lst + '
'; lst.unshift(-1); res += lst + '
'; lst.unshift(-2); res += lst + '
'; n = lst.pop(); res += lst + ' n='+n+'
'; n = lst.shift(); res += lst + ' n='+n+'
'; lst.splice(1,1); res += lst + '
'; lst.splice(1,0,2); res += lst + '
'; lst.reverse(); res += lst ;Однако, в ряде случаев, массив - не лучший выбор. В частности, при поиске в ширину необходимо часто добавлять элементы в начало списка. Эта операция для массива очень медленная, т.к. ему необходимо не только выделять дополнительную память, но и сдвигать все элементы массива вправо. Для таких случаев стоит использовать традиционные списки связанных между собой узлов ( = элементов) списка.
Класс List
Перейдём теперь к созданию класса List, который будет оперировать со списками. Зададим список при помощи набора структур вида , где nm - имя узла (может хранить любые данные), prv - указатель на предыдущий и nxt - на следующий узел. Если узлы не имеют указателя prv - то это односторонний (правосторонний) список List, иначе список двухсторонний и он реализован классом List2. Односторонний список чуть быстрее двухстороннего, однако, он заметно медленнее при удалении из конца (pop). Если используется эта функция, необходим List2.
Класс списка так же хранит указатели на первый - beg и последний - end его узел. Существует несколько способов интерпретации этих указателей. Мы подробнее рассмотрим более быстрый вариант, когда beg и end - это фиктивные узлы, а реальные находятся между ними.
Определим функцию, которая будет конструктором класса List:
function List(nm) < this.end = < nm:null, nxt:null >; // фиктивный последний элемент списка this.beg = < nm:null, nxt:this.end >; // фиктивный первый элементы списка this.length = 0; // число узлов в списке (пустой) if(nm !== undefined) // если есть аргумент nm this.push(nm); // сразу вставляем первый элемент >Функция List будет вызвана при объявлении объекта (экземпляра класса List) при помощи оператора new когда мы напишем: var lst = new List(); Теперь объект lst будет хранить данные списка, доступ к которым производится через точку: (lst.length и т.д.). Такие данные будет помнить каждый экземпляр класса (и у каждого они будут иметь свои значения). Выше подобным образом объявлены 3 переменные-свойства класса (end, beg и length).
В конструкторе класса List введен фиктивный указатель end, следующий за последним элементом списка. Фиктивный указатель beg через свойство beg.nxt ссылается на первый элемент списка. Так как список пока пустой, beg.nxt ссылается на end. Функцию (метод класса) push мы определим ниже.
Отметим, что поля nm и nxt инициализированы нулевым указателем null (напомним, что между undefined и null существует заметная разница). Фиктивный указатель end, при вставке в конец списка, будет превращаться в реальный последний узел. Поэтому, таким объявлением, мы предлагаем движку JavaScript сразу выделить память под объект с соответствующими полями, чтобы не пересоздавать его потом (при вставке). Это ускоряет работу с объектами (иногда в разы).
Методы класса
Кроме хранения данных, список должен выполнять определённые действия (иметь методы). Напишем, например, функцию, которая выводит список как строку в круглых скобках:
List.prototype.toString = function () < var n = this.beg.nxt; // сразу за beg стоит первый узел var st = "("; while(n !== this.end)< // пока нет фиктивного последнего st += n.nm + (n.nxt!== this.end ? "," : "");// выводим через запятую имена узлов n = n.nxt; // переходим к следующему узлу >return st+")"; >Конструкция (условие? значение_если_истина : значение_если_ложь), являющаяся компактной записью условного оператора if, служит для "ликвидации" запятой после последнего узла в списке.
Функция с именем toString может быть в любом классе. Если она определена, то экземпляр класса, когда этого требует соответствующий контекст, преобразуется в строку. Например, следующий скрипт:
var lst = new List(); document.write(lst);так как список пока пустой, выведет строку: . Функция toString есть и у массивов, поэтому их элементы можно выводить аналогичным образом.
Вставка элементов
Напишем теперь функцию, вставляющую элемент с именем nm в начало списка:
List.prototype.unshift = function (nm) < this.length++; // увеличиваем число узлов this.beg.nxt = ; // вставляемый элемент следует за beg >Так как мы хотим, чтобы список был похож на массив, при добавлении увеличивается переменная length (число элементов в списке). Затем (читая справа-налево) создаётся новый элемент-узел, который по указателю nxt ссылается на прежний первый элемент списка beg.nxt, а фиктивный узел beg по beg.nxt начинает ссылается на вновь добавленный узел. Аналогично выглядит функция, вставляющая элемент в конец списка:
List.prototype.push = function (nm) < this.length++; // увеличиваем число узлов this.end.nm = nm; // фиктивный end становится реальным this.end = this.end.nxt = < nm:null, nxt:null>;// и затем снова фиктивным >Последнюю строку также необходимо читать справа-налево: сначала создаётся новый фиктивный последний узел, на который по указателю end.nxt ссылается реальный последний узел (которым становится "старый" фиктивный). Затем указатель на новый фиктивный сохраняется в this.end.
Графически, последовательная вставка 0, 1, 2 в начало и конец списка выглядит следующим образом: (первые картинки в каждой строке изображают пустые списки):
Удаление элементов
Обсудим алгоритмы удаления элементов из списка. Удаление в правосторонних списках (есть только ссылки nxt) из начала не представляет труда:
List.prototype.shift = function () < if(this.length===0) return; // список пуст - вернём undefined this.length--; // уменьшаем число узлов this.beg = this.beg.nxt; // фиктивный beg ссылается на второй элемент return this.beg.nm; >Отметим одну тонкость. Возвращаемый функцией указатель на имя первого узла nm остаётся в фиктивном узле nm, даже, если вызывающей функцию shift программе он уже не нужен. При необходимости (если данные, хранимые в списке занимают много памяти) его можно освободить, написав lst.beg.nm=null.
Удаление из конца одностороннего списка, существенно более затратная операция, так как необходимо получить последний элемент (реальный) о котором указатель end ничего не знает:
List.prototype.pop = function () < if(this.length === 0) return; // список пуст - вернём undefined this.length--; // уменьшаем число узлов var n = this.beg.nxt; // начиная с первого реального узла, while(n.nxt !== this.end) // ищем реальный последний узел n = n.nxt; // переходя каждый раз к следующему this.end = n; // фиктивный сдвигаем влево на один return n.nm; >При частом выполнении этой операции, лучше использовать класс двунаправленного списка List2, в котором фиктивный финальный узел end (как и остальные узлы) хранит указатель на предыдущий элемент end.prv.
Ещё немного функций
Приведём несколько функций класса List, эффективность которых не столь высока, по сравнению с вставкой в начало или конец списка. Так, получение узла под номером pos (начиная с нуля) выглядит следующим образом:
List.prototype.node = function (pos) < var n = this.beg.nxt; // бежим от начала while(n !== this.end && pos-- >0) n = n.nxt; // переходим к следующему узлу return n; >JavaScript
i-- vs. --iДекриментирование справа (уменьшение значения на единицу): pos--, тоже, что pos = pos - 1. Однако, оно производится после участия переменной в выражении. Поэтому в цикле while сначала проводится сравнение (pos > 0), а только после этого уменьшается значение pos. Иначе ситуация обстоит с декриментированием слева: --pos
Упорядоченная вставка
Добавим ещё функцию, которая вставляет элемент в начало списка, но при этом следит за упорядоченностью элементов:
List.prototype.add = function (nm) < var n = this.beg; // бежим от начала while(n !== this.end && n.nxt.nm !==null && this.lt(n.nxt.nm, nm) ) n = n.nxt; this.length++; // увеличиваем число узлов n.nxt = ; // вставляемый элемент следует за n >Предполагается, что в общем случае имя узла nm не является числом или строкой, поэтому в цикле используется функция сравнения lt(a, b), которая должна возвращать true, если a < b, иначе (больше или равны) - false. Она задаётся извне (хотя по умолчанию она определена так, как в закомментированной строке):
lst = new List(); var arr = [1, 0, 5, 3, 0, 5, 8, 2]; //lst.lt = function(a, b) < return a < b; >for(var i=0; i < arr.length; i++ )< lst.add(arr[i]); document.write(arr[i],' → ' ,lst, '
'); >В дальнейшем мы рассмотрим более эффективные способы хранения упорядоченных данных.
Нефиктивные указатели
Рассмотрим кратко ещё один способ работы со списком, когда указатели beg и end реально ссылаются на первый и последний элементы списка (напрямую, а не через поле nxt). Оформим его в виде класса List1
function List1(nm) < this.beg = null; // указатель на первый элемент списка (пока в никуда) this.end = null; // указатель на последний элемент списка (пока в никуда) this.length = 0; // число узлов в списке if(nm !== undefined) // если есть аргумент nm this.push(nm); // сразу вставляем первый элемент с именем nm фу-ей push >Для удобства рядом с функциями-методами этого класса повторим аналогичные функции класса List. Добавление в начало списка имеет вид:
List1.prototype.unshift = function (nm) < if(this.length===0)< this.beg = this.end = < nm: nm >; this.length= 1; return; > this.length++; this.beg = ; >List.prototype.unshift = function (nm) < this.length++; this.beg.nxt = ; >Добавление в конец очереди:
List1.prototype.push = function (nm) < if(this.length===0)< this.beg = this.end = < nm: nm >; this.length= 1; return; > this.length++; this.end = this.end.nxt = ; >List.prototype.push = function (nm) < this.length++; this.end.nm = nm; this.end = this.end.nxt = < >; >Как мы увидим ниже, списки List1 по быстродействию заметно седленнее списков из класса List. К тому-же, функции вставки в классе List не требуют проверки того, что список пуст, поэтому выглядят более изящными.
Двухсторонние списки
Элементы (узлы) двухcторонних списков хранят в себе указатели, как на следующий элемент (nxt), так и на предыдущий (prv): Они реализованы классом List2, конструктор которого выглядит так:
function List2(nm) < this.end = < >; // указатель на фиктивный последний элемент списка this.beg = < nxt:this.end >; // указатель на фиктивный первый элементы списка this.end.prv = this.beg; this.length = 0; // число узлов в списке if(nm !== undefined) this.push(nm); >Функции добавления в начало и конец реализованы следующим образом:
List2.prototype.unshift = function (nm) < this.length++; this.beg.nxt = ; >List2.prototype.push = function (nm) < this.length++; this.end.nm = nm; this.end = this.end.nxt = < prv:this.end >; >Приведём также функции удаления из конца и начала:
List2.prototype.pop = function ()
List2.prototype.shift = function ()Тестирование быстродействия
Ниже тестируется вставка в начало и конец списка, реализованного массивом (медленный unshift) и правосторонними (List, List1) и двухсторонним списком (List2). Как организовать процесс тестирования описано в документе Speed.
добавлений раз: (ms) ( цикл: )
push | unshift | ||
---|---|---|---|
Array | 13 | 9088 | Обычный массив JavaScript |
List | 25 | 15 | Односторонний список List |
List2 | 30 | 25 | Двухсторонний список List2 |
List0 | 47 | 34 | Односторонний список List без инициализаций null |
List1 | 49 | 33 | Односторонний список List с "нефиктивными" beg и end |
Ещё один тест связан с последовательной вставкой всех элементов в конец, а затем их удалением из конца и начала списка:
добавлений раз: (ms) ( цикл: )
push,pop | push,shift | ||
---|---|---|---|
Array | 19 | 147 | Обычный массив JavaScript |
List | 10693 | 13 | Односторонний список List |
List2 | 22 | 30 | Двухсторонний список List2 |
Общий вывод такой: для операций push, unshift и shift, которые активно используются в алгоритмах поиска в ширину или глубину, стоит использовать класс List. Однако, если необходимо часто удалять элементы из конца списка pop, то List лучше не использовать.
Работа со списками
Подытожим. Методы работы со списками полностью аналогичны методам работы с массивами:
var res = ""; var lst = new List2(); for(var i=0; i < 7; i++) lst.push(i); res += 'число узлов:'+ lst.length+ '
' + lst+'
'; lst.reverse(); res += lst+'
'; res += lst.pop()+ ': ' + lst+'
'; res += lst.node(2).nm + '
'; lst.put(2,"a"); res += lst+'
'; res += lst.get(0)+'
'; lst.insert(0,"b"); res += lst+'
'; res += lst.remove(0) + ": " + lst+'
'; res += lst.remove(2) + ": " + lst+'
'; lst.reverse(); res += lst+'
' res += ListShow(lst); return res;
- toString() - вывести список как строку (nm0, nm1, n2. ).
- push(nm) - добавить узел с именем nm в конец списка.
- unshift(nm) - добавить узел с именем nm в начало списка.
- add(nm) - добавить узел с именем nm с начала списка, выдерживая его упорядоченным (функция lt).
- pop() - получить поле nm последнего узла и убрать его из списка.
- shift() - получить поле nm первого узла и убрать его из списка.
- reverse() - перевернуть последовательность узлов в списке.
- node(pos) - получить узел под номером pos в списке (начиная с нуля).
- get(pos) - получить значение поля nm узла на месте pos в списке (начиная с нуля).
- put(pos, nm) - поменять поле nm узла месте pos в списке (начиная с нуля).
- insert(pos, nm) - добавить узел с именем nm на место pos в списке (начиная с нуля), сдвинув всё вправо.
- remove(pos) - удалить узел на месте pos в списке (начиная с нуля) и вернуть его nm.