Способы хранения динамических данных PHP


Оглавление (нажмите, чтобы открыть):

Способы хранения динамических данных PHP

Здесь могла бы быть ваша реклама

Покинул форум
Сообщений всего: 4574
Дата рег-ции: Июль 2006
Откуда: Israel

Секрет
Теперь, когда вы уже наверняка второпях отправили свой запрос,
я расскажу вам простой секрет, который сэкономит вам уйму ожиданий,
даже если первый ответ по теме последуем сразу же.

Само собой я знаю что ответят мне тут же, и если я посмотрю
на сообщения на форуме, то пойму что в общем то я и не ошибаюсь.
Но еще я точно замечу, что очень мало тем, в которых всего два ответа :
вопрос автора и еще два сообщение вида Ответ + Спасибо

После этого приходится начинать уточнять этим неграмотным что мне надо.
Они что, сами читать не умеют? А уточнять приходится.
И иногда пока они переварят то что я им скажу проходит и не одна ночь..

Уверен что если бы я им сказал что у меня есть
фиолетовый квадрат, и нужно превратить его в синий треугольник
и я пытался взять кисточку, макнуть в банку и поводить ей по квадрату
но почему то кисточка не принимала цвет краски в банке,
то на мой вопрос — где взять правильные банки мне бы ответили гораздо быстрее
предложив её открыть, а не тратить еще стольник на жестянку.

Поэтому с тех пор я строю свои вопросы по проверенной давным давно схеме:
Что есть
Что нужно получить
Как я пытался
Почему или что у меня не получилось.

На последок как оно происходит на форумах

Новичок: Подскажите пожалуста самый крепкий сорт дерева! Весь инет перерыл, поиском пользовался!
Старожил: Объясни, зачем тебе понадобилось дерево? Сейчас оно в строительстве практически не используется.
Новичок: Я небоскрёб собираюсь строить. Хочу узнать, из какого дерева делать перекрытия между этажами!
Старожил: Какое дерево? Ты вообще соображаешь, что говоришь?
Новичок: Чем мне нравиться этот форум — из двух ответов ниодного конкретного. Одни вопросы неподелу!
Старожил: Не нравится — тебя здесь никто не держит. Но если ты не соображаешь, что из дерева небоскрёбы не строят, то лучше бы тебе сначала школу закончить.
Новичок: Не знаите — лучше молчите! У меня дедушка в деревянном доме живёт! У НЕГО НИЧЕГО НЕ ЛОМАЕТСЯ.
Но у него дом из сосны, а я понимаю, что для небоскрёба нужно дерево прочнее! Поэтому и спрашиваю. А от вас нормального ответа недождёшся.
Прохожий: Самое крепкое дерево — дуб. Вот тебе технология вымачивания дуба в солёной воде, она придаёт дубу особую прочность:
Новичок: Спасибо, братан! То что нужно.

Отредактировано модератором: Uchkuma, 26 Апреля, 2011 — 10:21:12

Лучший способ хранения динамических данных?

Я разрабатываю планшет для Android и нуждаюсь в советах по разработке моей постоянной системы хранения.

Я хочу, чтобы люди могли создавать разные категории для своих приложений (например, Media, Utilities, Social). Пользователи должны иметь возможность выбирать порядок своих приложений, а приложения могут отображаться более чем в одной категории. Пользователи могут обновлять порядок приложений, и заказ должен сохраняться при перезапуске. Я также хотел бы отслеживать, как часто запускаются приложения, так что у меня может быть автоматическая «наиболее используемая» категория.

У меня было 2 подхода, но не кажутся идеальными:

Сохраните список приложений в файле (JSON или другой), принимая во внимание имя и позицию пакета в списке. При необходимости введите этот файл и отсортируйте его по заказу

Сохраните список в базе данных SQLlite:

  • У вас есть таблица для каждой категории , Столбцы будут иметь имя_пакеты и list_position
  • Имеет одну таблицу со столбцом для каждой категории, которая сохраняет позицию этого приложения в этой категории (или null, если не присутствует). Когда создается новая категория, добавляется новый столбец (не поддерживается в комнате).

Вариант 1 Я считаю, что было бы сложно сохранить динамику и неуверенность эффективности, поэтому я предпочитаю вариант 2, потому что его просто обновлять и автоматически заказывать, но может быть слишком сложно использовать БД для этого.

Любые советы или другие возможные решения были бы замечательными! Благодаря

Выбор и запись динамических данных MySQL PHP — php

Это скорее вопрос дизайна, лучший способ сделать это.

Я пытаюсь создать приложение «инвентаризации» в своем проекте. Мне нужно разрешить людям добавлять неограниченное количество суб-данных.

Сейчас я не могу определить лучший способ хранения данных как доступных для поиска (поэтому я думаю, что blob отсутствует). Я динамически сохраняю то, что они называют param1->param2->param3->param4 например Box Row Section в BLOB, потому что на пользователя будет только одна схема.

Я храню данные о местоположении, которое нужно искать, поэтому, если мне нужно искать param1=1,param2=2,param3=3 я могу найти его легко. В настоящее время я могу искать в столбце, хранящем его как простой текстовый массив с LIKE или REGEX, является ли это лучшим способом для этого?

Это то, что у меня есть до сих пор:

    1 1
  • 30 авг 2020 2020-08-30 23:00:41
  • Andrew

1 ответ

Я бы установил таблицу следующим образом:

Поместите порядок параметров в последовательности (так что просто счет 1,2,3 и т.д.). Затем выполните рекурсивный запрос для каждого параметра.

РНР и MySQL

Обработка и хранение структурированных данных

Основным достоинством динамических сайтов, по сравнению со статическими, является возможность отделения данных от кода, отвечающего за их визуальное представление. Благодаря такому подходу, можно создавать сайты, формирующие страницы в зависимости от запросов посетителей. Однако для того, чтобы создать такой сайт, необходимо организовать хранилище структурированных данных. Это необходимо для того, чтобы программа, формирующая страницы, могла выбирать данные согласно критериям запроса, поступившего от посетителя сайта.

Самым простым способом организации хранилища данных является использование текстовых файлов. Можно хранить данные в одном или нескольких текстовых файлах, а внутри файлов отделять элементы данных друг от друга при помощи символов – разделителей. Такой способ организации данных может быть эффективен при небольшом количестве хранимой и обрабатываемой информации. Однако для того, чтобы оперировать данными, хранимыми в текстовых файлах, необходимо разработать набор подпрограмм для добавления, удаления, модификации и выборки данных. В случае, если структура данных будет изменена, подпрограммы управления данными придется переписывать.


Для того, чтобы избежать подобных трудностей, необходимо использовать базы данных. Оперировать данными, занесенными в базу данных (БД), можно при помощи СУБД – системы управления базой данных. СУБД предоставляют интерфейс, при помощи которого можно организовывать базы данных с произвольной внутренней структурой и управлять данными, расположенными в них.

В настоящий момент наиболее широкое распространение получили реляционные базы данных. Информация в них представляется в виде набора таблиц. Каждая таблица содержит одну или несколько колонок, которые описывают атрибуты данных, а каждая строка таблицы является экземпляром данных (подробнее реляционные БД будут рассмотрены в разделе «Построение реляционной базы данных»). В качестве языка управления базой данных используется язык структурированных запросов SQL.

Динамические сайты, использующие базу данных

Рассмотрим, как работает динамический сайт, использующий в качестве хранилища базу данных. Работа подобного сайта строится на взаимодействии трех элементов:

Броузера (клиентской программы для просмотра сайтов)

Веб-сервера (с интерпретатором языка PHP)

База данных (вместе с СУБД)

Схема работы сайта приведена на рисунке. Перечислим этапы работы динамического сайта, взаимодействующего с базой данных:

Броузер посылает запрос веб-серверу на формирование HTML- страницы

Веб-сервер запускает интерпретатор PHP для выполнения скрипта, формирующего HTML-страницу

В процессе работы скрипта осуществляется обращение к базе данных для выборки информации, соответствующей параметрам запроса, посланного броузером

На основе информации, выбранной из базы данных, PHP- скрипт формирует HTML-страницу

Веб-сервер возвращает броузеру сформированную HTML-страницу

Броузер интерпретирует HTML-инструкции, содержащиеся в теле полученной страницы и выводит ее содержимое на экран монитора

Запись наборов данных в общую память с помощью PHP

Пошаговое руководство по использованию общей памяти PHP в качестве накопителя

Обзор

Общая память представляет собой эффективный способ обмена данными между приложениями в одной и той же машине. Один процесс создает сегмент общей памяти, к которому могут обращаться другие процессы, если у них есть нужные разрешения. Каждый сегмент получает уникальный идентификатор shmid, указывающий область физической памяти, где другие процессы могут работать с ним. После создания сегмент становится доступным для других процессов в той же машине, имеющих соответствующие разрешения, которые могут выполнять операции чтения, записи и удаления данных.

Это означает, что приложение, написанное на языке C, может обмениваться информацией с приложением, написанным на другом языке, таком как Java™ или PHP. Они могут обмениваться информацией, если только способны получить и понять эту информацию. Общая память широко применяется в реализациях, доступных для большинства языков, поэтому получение доступа не должно быть проблемой. Что касается понимания информации, то можно использовать стандартный формат, такой как XML или JSON.

Общая память представляет собой быстрый способ обмена данными между процессами, главным образом потому, что после создания сегментов ядро операционной системы не принимает никакого участия в процессе передачи данных. Подобные методы часто называют «межпроцессным взаимодействием» (interprocess communication — IPC). В число других методов IPC входят конвейеры, очереди сообщений, RPC и сокеты. Такой быстрый и надежный способ обмена данными между приложениями неоценим при работе с экосистемой приложений, которым нужно общаться друг с другом. Обычный метод использования баз данных для обмена информацией между приложениями часто приводит к медленной обработке запросов и даже блокированию ввода-вывода в зависимости от размера экосистемы. При работе с общей памятью операции ввода-вывода, замедляющие процесс, отсутствуют.

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

Мастер Йода рекомендует:  Расширенное использование вложений WordPress Создание страниц галерей по категориям

Общая память и PHP

В PHP есть широкий набор расширений, в том числе для работы с общей памятью. Но разработчики могут легко манипулировать сегментами с помощью нескольких общих функций, не устанавливая никаких расширений.

Создание сегментов

Функции общей памяти аналогичны функциям управления файлами, только вместо потоков вы работаете с идентификаторами доступа к общей памяти. Первым примером служит функция shmop_open , которая позволяет открыть существующий или создать новый сегмент. Эта функция очень похожа на классическую функцию fopen , которая открывает потоки для манипуляции файлами, возвращая ресурс, доступный для использования другими функциями, выполняющими операции чтения или записи в этот открытый поток. Рассмотрим функцию shmop_open , приведенную в листинге 1.

Листинг 1. Функция shmop_open

Первый параметр ― system ID. Это число, определяющее сегмент общей памяти в системе. Второй параметр ― режим доступа, который очень похож на режим доступа функции fopen . Доступ к сегменту можно получить четырьмя способами:

  • режим «а» позволяет обращаться к сегменту только для чтения;
  • режим «w» позволяет обращаться к сегменту для чтения и записи;
  • в режиме «c» создается новый сегмент, а если он уже существует, предпринимается попытка открыть его для чтения и записи;
  • режим «n» создает новый сегмент, а если он уже существует, выдается сообщение об ошибке.

Третий параметр ― разрешения для сегмента. Здесь нужно указать восьмеричное число.

Четвертый параметр указывает размер сегмента в байтах. Прежде чем записывать данные в сегмент, необходимо выделить нужное количество байтов в нем.

Обратите внимание, что эта функция возвращает идентификационный номер, который можно использовать с другими функциями для манипулирования сегментом общей памяти. Это идентификатор доступа к общей памяти, отличный от системного идентификатора, передаваемого в качестве параметра. Не путайте их. В случае неудачи функция shmop_open возвратит значение FALSE.

Запись в сегменты

Для записи данных в блок общей памяти используйте функцию shmop_write . Она очень проста в применении и принимает всего три параметра. См. листинг 2.

Листинг 2. Применение функции shmop_write для записи в блок общей памяти

Она похожа на функцию fwrite , которая принимает два параметра: открытый ресурс потока, возвращаемый функцией fopen , и данные, подлежащие записи. Функция shmop_write делает то же самое.

Первый параметр — идентификатор, возвращаемый функцией shmop_open , ― определяет блок общей памяти, с которым вы собираетесь работать. Второй параметр — это данные, которые нужно хранить и, наконец, третий параметр определяет адрес начала записи. По умолчанию это 0, так что запись начинается с самого начала. В случае неудачи эта функция возвращает значение FALSE и количество успешно записанных байтов.

Чтение из сегментов

Чтение из сегментов общей памяти ― простая процедура. Достаточно открыть сегмент и воспользоваться функцией shmop_read . Она принимает несколько параметров и работает аналогично функции fread . В листинге 3 приведен пример чтения содержимого файла в PHP.

Листинг 3. Использование функции shmop_read для чтения содержимого файла


Чтение содержимого сегмента общей памяти выполняется аналогично, как показано в листинге 4.

Листинг 4. Чтение содержимого сегмента общей памяти

Обратите внимание на параметры. Функция shmop_read принимает идентификатор, возвращенный функцией shmop_open , который нам уже знаком, и два других параметра. Второй параметр ― место, с которого нужно начать чтение сегмента; а третий ― количество байтов, которое требуется считать. Второй параметр может всегда быть 0, началом данных, зато третий может вызвать проблему, так как мы можем не знать, сколько байтов хотим считать.

Это очень похоже на поведение функции fread , которая принимает два параметра: открытый ресурс потока, возвращаемый функцией fopen , и количество байтов, которые требуется считать из этого потока. Чтобы прочитать файл во всей его полноте, используйте функцию filesize , которая возвращает количество байтов в файле.

К счастью, при работе с сегментами общей памяти функция shmop_size , подобно функции filesize , возвращает размер сегмента в байтах. (См. листинг 5).

Листинг 5. Функция shmop_size возвращает размер сегмента в байтах

Удаление сегмента

Мы знаем, как открывать, записывать и считывать сегменты общей памяти. Для завершения нашего класса по CRUD осталось только узнать, как удалять сегменты. Эту задачу можно легко решить с помощью функции shmop_delete , которой нужен только один параметр: идентификатор сегмента общей памяти, который мы хотим удалить.

Листинг 6. Функция shmop_delete помечает сегмент для удаления

На самом деле она не удаляет сегмент. Она помечает его для удаления, поскольку сегмент общей памяти не может быть удален, пока существуют другие использующие его процессы. Функция shmop_delete помечает сегмент для удаления и мешает любому другому процессу открыть его. Теперь, чтобы удалить сегмент, его нужно закрыть.

Закрытие сегмента

При открытии сегмента общей памяти вы «присоединяетесь» к нему. Присоединившись к сегменту, в него можно записывать данные и считывать их из него, но когда работа закончена, нужно отсоединиться от сегмента. Это делается с помощью функции shmop_close , как показано в листинге 7.

Она очень похожа на функцию fclose при работе с файлами. После открытия потока с файлом и чтения или записи в него этот поток нужно закрыть, иначе произойдет блокировка.

Листинг 7. Использование функции shmop_close для отсоединения от сегмента

Использование общей памяти в качестве накопителя

Обладая базовыми знаниями в области общей памяти и освоив основные операции CRUD над сегментами общей памяти, можно приступать к практическому применению этих знаний. Общую память можно использовать в качестве уникальной альтернативы накопителю, которая предлагает такие преимущества, как быстрые операции чтения/записи и взаимодействие процессов. Для Web-приложения это означает:

  • буферную память (хранение запросов к базе данных, данных Web-сервисов, внешних данных);
  • память сеансов;
  • обмен данными между приложениями.

Прежде чем продолжить, я хотел бы представить небольшую библиотеку SimpleSHM. Это компактный уровень абстракции для работы с общей памятью в PHP, который позволяет легко манипулировать сегментами с применением объектно-ориентированного подхода. Эта библиотека помогает создавать гораздо более чистый код при написании небольших приложений, использующих общую память для хранения данных. Для начала работы с SimpleSHM загрузите tarball-файл со страницы GitHub.

Существует три вида операций: чтение, запись и удаление. Простое создание экземпляра объекта класса обеспечит открытие сегмента общей памяти. Основные моменты отражены в листинге 8.

Листинг 8. Основы использования SimpleSHM

Обратите внимание, что идентификатор классу не передается. Раз идентификатор не передается, номер будет выбран случайным образом, и новый сегмент откроется с этим номером. Номер можно передать конструктору в качестве параметра, чтобы сегмент открывался или создавался с конкретным значением ID, как показано в листинге 9.

Листинг 9. Открытие заданного сегмента

Магический метод __destructor заботится о вызове функции shmop_close по отношению к сегменту, чтобы отключить объект и отсоединить его от сегмента. Назовем это «SimpleSHM 101». Теперь воспользуемся им для более высокой цели: использования общей памяти в качестве накопителя. Для хранения наборов данных требуется сериализация, поскольку в памяти нельзя хранить массивы или объекты. Здесь для сериализации используется JSON, но подойдет и любой другой метод, например, XML или встроенные функции сериализации PHP. Пример приведен в листинге 10.

Листинг 10. Использование общей памяти в качестве накопителя

Мы успешно преобразовали массив в строку JSON, сохранили его в блоке общей памяти, считали, преобразовали строку JSON обратно в массив и отобразили его. Все это кажется тривиальным, но только представите себе, какие возможности открывает этот фрагмент. Его можно использовать для хранения результатов запросов к Web-сервису, базам данных или даже в качестве буфера процессора текстовых шаблонов. Запись и чтение из памяти обеспечит гораздо более высокую производительность, чем запись и чтение с диска.

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

Заключение

Эта статья охватывает большую часть инструментов PHP для работы с сегментами общей памяти и объясняет, как функционирует общая память. Более того, она содержит предложения по совершенствованию Web-приложений и обращает внимание на некоторые факторы, которые нужно иметь в виду при создании Web-приложений. Идеи и рекомендации по их осуществлению станут отправной точкой для читателя. А построенная нами базовая модель поможет ему обдумать более сложные функции и решения.

Что дальше?

Мы указали некоторые общие задачи, для решения которых идеально подходит общая память, такие как кэширование, совместное использование сеанса и обмен данными между приложениями. Это введение в работу с общей памятью открывает перед читателем возможности по исследованию гораздо более элегантных решений типичных задач. Не стесняйтесь расширять текущую реализацию SimpleSHM в соответствии со своими потребностями и вносить эти изменения в проект.

Ресурсы для скачивания

Похожие темы

  • Оригинал статьи: Store datasets directly in shared memory with PHP.
  • Книга Майкла Керриска Интерфейс программирования Linux содержит отличные главы о межпроцессном взаимодействии, а главы 45-48 посвящены IPC System V.
  • В статье Дэйва Маршалла IPC: общая память описан интересный и прагматический подход к функциям общей памяти на языке Си.
  • Книга Ричарда Стивенса Программирование сети UNIX содержит отличный технический материал и описание нескольких реализаций на языке C. Познакомьтесь с выдержкой из главы этой книги.
  • Следите за developerWorks в Твиттере.

Комментарии


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

Алгоритмы и структуры данных для начинающих: связный список

Первая структура данных, которую мы рассмотрим — связный список. На то есть две причины: первое — связный список используется практически везде — от ОС до игр, и второе — на его основе строится множество других структур данных.

Связный список

Основное назначение связного списка — предоставление механизма для хранения и доступа к произвольному количеству данных. Как следует из названия, это достигается связыванием данных вместе в список.

Прежде чем мы перейдем к рассмотрению связного списка, давайте вспомним, как хранятся данные в массиве.

Мастер Йода рекомендует:  Упрощение Python кода с помощью знакомых инструментов

Как показано на рисунке, данные в массиве хранятся в непрерывном участке памяти, разделенном на ячейки определенного размера. Доступ к данным в ячейках осуществляется по ссылке на их расположение — индексу.

Это отличный способ хранить данные. Большинство языков программирования позволяют так или иначе выделить память в виде массива и оперировать его содержимым. Последовательное хранение данных увеличивает производительность (data locality), позволяет легко итерироваться по содержимому и получать доступ к произвольному элементу по индексу.

«Росбанк», Москва, до 60 000 ₽ (до налогов)

Тем не менее, иногда массив — не самая подходящая структура.

Предположим, что у нашей программы следующие требования:

  • Прочесть некоторое количество целых чисел из источника (метод NextValue ), пока не встретится число 0xFFFF .
  • Передать считанные числа в метод ProcessItems

Поскольку в требованиях указано, что считанные числа передаются в метод ProcessItems за один раз, очевидным решение будет массив целых чисел:

У этого решения есть ряд проблем, но самая очевидная из них — что случится, если будет необходимо прочесть больше 20 значений? В данной реализации значения с 21 и далее просто проигнорируются. Можно выделить больше памяти — 200 или 2000 элементов. Можно дать пользователю возможность выбрать размер массива. Или выделить память под новый массив большего размера при заполнении старого и скопировать элементы. Но все эти решения усложняют код и бесполезно тратят память.

Нам нужна коллекция, которая позволяет добавить произвольное число элементов и перебрать их в порядке добавления. Размер коллекции должен быть неограничен, а произвольный доступ нам не нужен. Нам нужен связный список.

Прежде чем перейти к его реализации, давайте посмотрим на то, как могло бы выглядеть решение нашей задачи.

Обратите внимание: проблем, присущих первому варианту решения больше нет — мы не можем выделить недостаточно или, наоборот, слишком много памяти под массив.

Кроме того, из этого кода можно увидеть, что наш список будет принимать параметр типа и реализовывать интерфейс IEnumerable

Реализация класса LinkedList

Класс Node

В основе связного списка лежит понятие узла, или элемента (Node). Узел — это контейнер, который позволяет хранить данные и получать следующий узел.

В самом простом случае класс Node можно реализовать так:

Теперь мы можем создать примитивный связный список. Выделим память под три узла ( first , middle , last ) и соединим их последовательно:

Теперь у нас есть список из трех элементов, начиная с first и заканчивая last . Поле Next последнего узла имеет значение null , что показывает, что это последний элемент. С этим списком уже можно производить различные операции. Например, напечатать данные из каждого элемента:

Метод PrintList итерируется по элементам списка: печатает значение поля Value и переходит к следующему узлу по ссылке в поле Next .

Теперь, когда мы знаем, как должен выглядеть узел связанного списка, давайте посмотрим на пример реализации класса LinkedListNode .

Класс LinkedList

Прежде чем реализовывать наш связный список, нужно понять, как мы будем с ним работать.

Ранее мы увидели, что коллекция должна поддерживать любой тип данных, а значит, нам нужно реализовать обобщенный интерфейс.

Поскольку мы используем платформу .NET, имеет смысл реализовать наш класс таким образом, чтобы его поведение было похоже на поведение встроенных коллекций. Самый простой способ сделать это — реализовать интерфейс ICollection . Заметьте, что мы реализуем ICollection , а не Ilist , поскольку интерфейс Ilist позволяет получать доступ к элементам по индексу. Несмотря на то, что произвольный доступ к элементам в целом полезен, его невозможно эффективно реализовать в связном списке.

Учитывая все вышесказанное, давайте набросаем примерный план класса, а затем заполним недостающие методы.

Метод Add

  • Поведение: Добавляет элемент в конец списка.
  • Сложность: O(1)

Добавление элемента в связный список производится в три этапа:

  1. Создать экземпляр класса LinkedListNode .
  2. Найти последний узел списка.
  3. Установить значение поля Next последнего узла списка так, чтобы оно указывало на созданный узел.


Основная сложность заключается в том, чтобы найти последний узел списка. Можно сделать это двумя способами. Первый — сохранять указатель на первый узел списка и перебирать узлы, пока не дойдем до последнего. В этом случае нам не требуется сохранять указатель на последний узел, что позволяет использовать меньше памяти (в зависимости от размера указателя на вашей платформе), но требует прохода по всему списку при каждом добавлении узла. Это значит, что метод Add займет O(n) времени.

Второй метод заключается в сохранении указателя на последний узел списка, и тогда при добавлении нового узла мы поменяем указатель так, чтобы он указывал на новый узел. Этот способ предпочтительней, поскольку выполняется за O(1) времени.

Первое, что нам необходимо сделать — добавить два приватных поля в класс LinkedList : ссылки на первый (head) и последний (tail) узлы.

Теперь мы можем добавить метод, который выполняет три необходимых шага.

Сначала мы создаем экземпляр класса LinkedListNode . Затем проверяем, является ли список пустым. Если список пуст, мы просто устанавливаем значения полей _head и _tail так, чтобы они указывали на новый узел. Этот узел в данном случае будет являться одновременно и первым, и последним в списке. Если список не пуст, узел добавляется в конец списка, а поле _tail теперь указывает на новый конец списка.

Поле Count инкрементируется при добавлении узла для того, чтобы сохранялся контракт интерфейса ICollection . Поле Count возвращает точное количество элементов списка.

Метод Remove

  • Поведение: Удаляет первый элемент списка со значением, равным переданному. Возвращает true , если элемент был удален и false в противном случае.
  • Сложность: O(n)

Прежде чем разбирать метод Remove , давайте посмотрим, чего мы хотим добиться. На следующем рисунке список с четырьмя элементами. Мы удаляем элемент со значением «3».

После удаления узла поле Next узла со значением «2» будет указывать на узел со значением «4».

Основной алгоритм удаления элемента такой:

  1. Найти узел, который необходимо удалить.
  2. Изменить значение поля Next предыдущего узла так, чтобы оно указывало на узел, следующий за удаляемым.

Как всегда, основная проблема кроется в мелочах. Вот некоторые из случаев, которые необходимо предусмотреть:

  • Список может быть пустым, или значение, которое мы передаем в метод может не присутствовать в списке. В этом случает список останется без изменений.
  • Удаляемый узел может быть единственным в списке. В этом случае мы установим значения полей _head и _tail равными null .
  • Удаляемый узел будет в начале списка. В этом случае мы записываем в _head ссылку на следующий узел.
  • Удаляемый узел будет в середине списка.
  • Удаляемый узел будет в конце списка. В этом случае мы записываем в _tail ссылку на предпоследний узел, а в его поле Next записываем null .

Поле Count декрементируется при удалении узла.

Метод Contains

  • Поведение: Возвращает true или false в зависимости от того, присутствует ли искомый элемент в списке.
  • Сложность: O(n)

Метод Contains достаточно простой. Он просматривает каждый элемент списка, от первого до последнего, и возвращает true как только найдет узел, чье значение равно переданному параметру. Если такой узел не найден, и метод дошел до конца списка, то возвращается false .

Метод GetEnumerator

  • Поведение: Возвращает экземпляр IEnumerator , который позволяет итерироваться по элементам списка.
  • Сложность: Получение итератора — O(1). Проход по всем элементам — O(n).

Возвращаемый итератор проходит по всему списку от первого до последнего узла и возвращает значение каждого элемента с помощью ключевого слова yield .

Метод Clear

  • Поведение: Удаляет все элементы из списка.
  • Сложность: O(1)

Метод Clear просто устанавливает значения полей _head и _tail равными null . Поскольку C# — язык с автоматическим управлением памятью, нет необходимости явно удалять неиспользуемые узлы. Клиент, вызывающий метод, должен убедиться в корректном удалении значений узлов, если это необходимо.

Метод CopyTo

  • Поведение: Копирует содержимое списка в указанный массив, начиная с указанного индекса.

  • Сложность: O(n)

Метод CopyTo проходит по списку и копирует элементы в массив с помощью присваивания. Клиент, вызывающий метод должен убедиться, что массив имеет достаточный размер для того, чтобы вместить все элементы списка.

Метод Count

  • Поведение: Возвращает количество элементов списка. Возвращает 0, если список пустой.
  • Сложность: O(1)

Count — поле с публичным геттером и приватным сеттером. Изменение его значения осуществляется в методах Add , Remove и Clear .

Метод IsReadOnly

  • Поведение: Возвращает true , если список только для чтения.
  • Сложность: O(1)

Двусвязный список

Связный список, который мы только что создали, называется также «односвязным». Это значит, что между узлами только одна связь в единственном направлении от первого узла к последнему. Есть также достаточно распространенный вариант списка, который предоставляет доступ к обоим концам — двусвязный список.

Для того, чтобы создать двусвязный список, мы должны добавить в класс LinkedListNode поле Previous , которое будет содержать ссылку на предыдущий элемент списка.

Далее мы рассмотрим только отличия в реализации односвязного и двусвязного списка.

Класс Node

Единственное изменение, которое надо внести в класс LinkedListNode — добавить поле со ссылкой на предыдущий узел.

Метод AddFirst

В то время, как односвязный список позволяет добавлять элементы только в конец, используя двусвязный список мы можем добавлять элементы как в начало, так и в конец, с помощью методов AddFirst и AddLast соответственно. Метод ICollection .Add будет вызывать AddLast для совместимости с односвязным списком.

  • Поведение: Добавляет переданный элемент в начало списка.
  • Сложность: O(1)

При добавлении элемента в начало списка последовательность действий примерно такая же, как и при добавлении элемента в односвязный список.

  1. Установить значение поля Next в новом узле так, чтобы оно указывало на бывший первый узел.
  2. Установить значение поля Previous в бывшем первом узле так, чтобы оно указывало на новый узел.
  3. Обновить поле _tail при необходимости и инкрементировать поле Count

Метод AddLast

  • Поведение: Добавляет переданный элемент в конец списка.
  • Сложность: O(1)

Добавление узла в конец списка легче, чем в начало. Мы просто создаем новый узел и обновляем поля _head и _tail , а затем инкрементируем поле Count .

Как было сказано ранее, ICollection .Add просто зовет AddLast .

Метод RemoveFirst

Как и метод Add , Remove будет разделен на два метода, позволяющих удалять элементы из начала и из конца списка. Метод ICollection .Remove будет также удалять элементы из начала, но теперь будет еще обновлять поля Previous в тех узлах, где это необходимо.

  • Поведение: Удаляет первый элемент списка. Если список пуст, не делает ничего. Возвращает true , если элемент был удален и false в противном случае.
  • Сложность: O(1)
Мастер Йода рекомендует:  ТОП-8 инструментов для анализа данных в 2020 году

RemoveFirst устанавливает ссылку head на второй узел списка и обнуляет поле Previous этого узла, удаляя таким образом все ссылки на предыдущий первый узел. Если список был пуст или содержал только один элемент, то поля _head и _tail становятся равны null .

Метод RemoveLast


  • Поведение: Удаляет последний элемент списка. Если список пуст, не делает ничего. Возвращает true , если элемент был удален и false в противном случае.
  • Сложность: O(1)

RemoveLast устанавливает значение поля _tail так, чтобы оно указывало на предпоследний элемент списка и, таким образом, удаляет последний элемент. Если список был пустым, или содержал только один элемент, то поля _head и _tail становятся равны null .

Метод Remove

  • Поведение: Удаляет первый элемент списка со значением, равным переданному. Возвращает true , если элемент был удален и false в противном случае.
  • Сложность: O(n)

Метод ICollection .Remove() почти такой же, как и в односвязном списке. Единственное отличие — теперь нам необходимо поменять значение поля Previous при удалении узла. Для того, чтобы не повторять код, этот метод зовет RemoveFirst при удалении первого узла.

Зачем нужен двусвязный список?

Итак, мы можем добавлять элементы в начало списка и в его конец. Что нам это дает? В том виде, в котором он реализован сейчас, нет особых преимуществ перед обычным односвязным списком. Но если добавить геттеры для полей head и tail , пользователь нашего списка сможет реализовать множество различных алгоритмов.

Так мы сможем итерироваться по списку вручную, в том числе от последнего элемента к первому.

В этом примере мы используем поля Tail и Previous для того, чтобы обойти список задом наперед.

Кроме того, двусвязный список позволяет легко реализовать двусвязную очередь, которая, в свою очередь, является строительным блоком для других структур данных. Мы вернемся к ней позже, в соответствующей части.

Продолжение следует

На этом мы заканчиваем разбор связных списков. В следующий раз мы подробно разберем строение векторов (array list).

codedokode / Как хранить в БД древовидные структуры (паста).md

Эта версия статьи устарела. Новая версия статьи перенесена по адресу: https://github.com/codedokode/pasta/blob/master/db/trees.md

Как хранить в БД древовидные структуры

Древовидные структуры — это такие структуры, где есть родители и дети, например, каталог товаров:

Типичные задачи, которые встречаются при работе с такими структурами:

  • выбрать всех детей элемента
  • выбрать всех потомков (детей и их детей) элемента
  • выбрать цепочку предков элемента (родитель, его родитель, и так далее)
  • переместить элемент (и его потомков) из одной группы в другую
  • удалить элемент из таблицы (со всеми потомками)

У каждой записи есть идентификатор — уникальное число, он на схеме написан в скобках (думаю, это ты и так знаешь). Рассмотрим способы хранения таких данных.

1) Добавить колонку parent_id (метод Adjacency List)

Мы добавляем к каждой записи колонку parent_id (и индекс на нее), которая хранит id родительской записи (если родителя нет — NULL). Это самый простой, но самый неэффективный способ. Вот как будет выглядеть вышеприведенное дерево:

Выбрать всех детей просто: SELECT WHERE parent_ >, но другие операции требуют выполнения нескольких запросов и на больших деревьях особо неэффективны. Например, выбор всех потомков элемента с идентификатором :id

  • выбрать список детей :id ( SELECT WHERE parent_ >)
  • выбрать список их детей ( SELECT WHERE parent_id IN (:children1) )
  • выбрать список детей детей ( SELECT WHERE parent_id IN (:children2) )

И так, пока мы не дойдем до самого младшего ребенка. После этого надо еще отсортировать и объединить результаты в дерево.

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

Иногда еще добавляют поле depth , указывющее глубину вложенности, но его надо не забыть обновлять при перемещении ветки.

2) Closure table — усовершенствование предыдущего способа

В этом способы мы так же добавляет поле parent_id , но для оптимизации рекурсивных выборок создаем дополнительную таблицу, в которой храним всех потомков (детей и их детей) и их глубину относительно родителя каждой записи. Поясню. Дополнительная таблица выглядит так:

Чтобы узнать всех потомков записи, мы (в отличие от предыдущего способа), делаем запрос к дополнительной таблице: SELECT child_ >, получаем id потомков и выбираем их их основной таблицы: SELECT WHERE id IN (:children) . Если таблицы хранятся в одной БД, запросы можно объединить в один с использованием JOIN.

Данные потом надо будет вручную отсортировать в дерево.

Узнать список предков можно тоже одним запросом к таблице связей: SELECT parent_ >

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

Плюсы: относительная простота, быстрота выборок.

Идея в том, что мы добавляем к каждой записи поля parent_id , depth , left , right и выстраиваем записи хитрым образом. После этого выборка всех потомков (причем уже отсортированных в нужном порядке) делаетсяпростым запросом вида SELECT WHERE left >= :a AND right

Минусы: необходимость пересчитывать left / right при вставке записей в середину или удалении, сложное перемещение веток, сложность в понимании.

Плюсы: скорость выборки

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

4) Materialized Path

Идея в том, что записи в пределах одной ветки нумеруются по порядку и в каждую запись добавляется поле path, содержащее полный список родителей. Напоминает способ нумерации глав в книгах. Пример:

При этом способе path хранится в поле вроде TEXT или BINARY, по нему делается индекс. Выбрать всех потомков можно запросом SELECT WHERE path LIKE ‘1.1.%’ ORDER BY path , который использует индекс.

Плюс: записи выбираются уже отсортированными в нужном порядке. Простота решения и скорость выборок высокая (1 запрос). Быстрая вставка.

Минусы: при вставке записи в середину надо пересчитывать номера и пути следующих за ней. При удалении ветки, возможно тоже. При перемещении ветки надо делать сложные расчеты. Глубина дерева и число детей у родителя ограничены выделенным дял них местом и длиной path

Этот способ отлично подходит для древовидных комментариев.

Теория: google it

5) Использовать БД с поддержкой иерархии

Я в этом не разбираюсь, может кто-то расскажет, какие есть возможности в БД для нативной поддержки деревьев. Вроде что-то такое есть в MSSQL и Oracle. Только хотелось бы услышать, как именно это оптимизируется и какой метод хранения используется, а не общие слова.

Динамические страницы в PHP

Из предыдущих уроков мы узнали, что с помощью метода GET можно передавать некоторые параметры прямо в URL. Однако ничто не мешает нам делать это без форм, просто перечисляя их в URL.

Мы можем передавать параметры через URL. И мы можем получить эти параметры прямо в скрипте. Так что нам мешает показывать пользователю разные страницы в зависимости от параметров в URL?

Создание динамической страницы

Чтобы показывать пользователю разные страницы, нужно подготовить контент. Пусть он лежит в многомерном массиве:

Динамический параметр в URL будет называться >$_GET[‘id’] . Мы могли бы добавить поле id каждому элементу массива, но тогда пришлось бы перебирать все элементы и искать подмассив с нужным id. Поэтому гораздо проще в качестве id использовать ключи главного массива.

Проще говоря, мы берём id и пытаемся найти статью с таким ключом в массиве $articles. Выглядит это следующим образом:

Осталось только набросать вывод меню и проверку id на корректность. Получается настоящий php-роутер!

Выбор и запись динамических данных MySQL PHP — php

Это скорее вопрос дизайна, лучший способ сделать это.

Я пытаюсь создать приложение «инвентаризации» в своем проекте. Мне нужно разрешить людям добавлять неограниченное количество суб-данных.

Сейчас я не могу определить лучший способ хранения данных как доступных для поиска (поэтому я думаю, что blob отсутствует). Я динамически сохраняю то, что они называют param1->param2->param3->param4 например Box Row Section в BLOB, потому что на пользователя будет только одна схема.

Я храню данные о местоположении, которое нужно искать, поэтому, если мне нужно искать param1=1,param2=2,param3=3 я могу найти его легко. В настоящее время я могу искать в столбце, хранящем его как простой текстовый массив с LIKE или REGEX, является ли это лучшим способом для этого?

Это то, что у меня есть до сих пор:

    2 1
  • 30 авг 2020 2020-08-30 23:00:41
  • Andrew

1 ответ

Я бы установил таблицу следующим образом:

Поместите порядок параметров в последовательности (так что просто счет 1,2,3 и т.д.). Затем выполните рекурсивный запрос для каждого параметра.

Лучший способ хранения динамических данных?

Я разрабатываю планшет для Android и нуждаюсь в советах по разработке моей постоянной системы хранения.

Я хочу, чтобы люди могли создавать разные категории для своих приложений (например, Media, Utilities, Social). Пользователи должны иметь возможность выбирать порядок своих приложений, а приложения могут отображаться более чем в одной категории. Пользователи могут обновлять порядок приложений, и заказ должен сохраняться при перезапуске. Я также хотел бы отслеживать, как часто запускаются приложения, так что у меня может быть автоматическая «наиболее используемая» категория.

У меня было 2 подхода, но не кажутся идеальными:

Сохраните список приложений в файле (JSON или другой), принимая во внимание имя и позицию пакета в списке. При необходимости введите этот файл и отсортируйте его по заказу

Сохраните список в базе данных SQLlite:

  • У вас есть таблица для каждой категории , Столбцы будут иметь имя_пакеты и list_position
  • Имеет одну таблицу со столбцом для каждой категории, которая сохраняет позицию этого приложения в этой категории (или null, если не присутствует). Когда создается новая категория, добавляется новый столбец (не поддерживается в комнате).

Вариант 1 Я считаю, что было бы сложно сохранить динамику и неуверенность эффективности, поэтому я предпочитаю вариант 2, потому что его просто обновлять и автоматически заказывать, но может быть слишком сложно использовать БД для этого.

Любые советы или другие возможные решения были бы замечательными! Благодаря

Добавить комментарий