Які основні структури даних використовуються для Redis?


305

Я намагаюся відповісти на два питання у остаточному списку:

  1. Які основні структури даних використовуються для Redis?
  2. І які основні переваги / недоліки / випадки використання для кожного типу?

Отже, я читав, що списки Redis реально реалізовані із пов'язаними списками. Але для інших типів я не в змозі викопати будь-яку інформацію. Крім того, якби хтось натрапив на це питання і не мав підсумок високих рівнів плюсів і мінусів зміни або доступу до різних структур даних, вони мали б повний перелік, коли найкраще використовувати конкретні типи для посилання.

Зокрема, я хочу окреслити всі типи: рядок, список, набір, zset та хеш.

О, я переглянув цю статтю, серед інших, поки що:


7
Як використовувати сервер - дрібниці? Як визначити, коли використовувати одну програмувальну структуру над іншою? Це безпосередньо стосується програмування, оскільки я використовував різні типи для різних цілей.
Гомер6

2
Як користуватися сервером не обов’язково дрібниці, але це поза темою - і це не те, що ви запитували. Які структури даних використовувати для конкретних цілей було б актуальним, але це не те, про що ви запитували. Що було використано в Redis, це дрібниці, відсутні додаткові міркування про те, чому вони використовували певну структуру в певній ситуації - тоді ми повертаємось до того, що я вже сказав, що буде актуальним, і що Redis трапляється робити не має значення.
Джеррі Труну

5
У темі чітко зазначено: "Що таке структури даних і коли слід використовувати різні типи?" Як це поза темою? Ви хочете сказати, що знання про пов'язані списки, хеші та масиви не має значення для програмування? Тому що я б заперечував, що вони мають безпосереднє значення - особливо на сервері, призначеному в першу чергу для продуктивності. Крім того, вони актуальні, оскільки неправильний вибір може означати значно меншу продуктивність від однієї програми до іншої.
Homer6

19
відповідь антиреза викуповує це питання. закрити на шкоду програмістам та переробникам користувачів скрізь.
Джон Шихан

75
@JerryCoffin з усією повагою, redis - це інструмент розробки програмного забезпечення, і питання щодо інструментів розробки програмного забезпечення є твердою темою. Те, що "ви можете отримати відповідь від джерела", не є тісною причиною ... для отримання відповіді від джерела знадобилися б години. І Redis дуже широко використовується, тому це питання не надто локалізовано. Переповнення стека - це все, щоб дізнатися про програмування та запитати, яку структуру даних використовує надзвичайно популярний інструмент програмування, що сприяє досягненню цієї мети. Коротше кажучи, я не знаходжу жодної причини, щоб закрити це питання.
Джоель Спольський

Відповіді:


612

Я спробую відповісти на ваше запитання, але я розпочну з того, що спочатку може виглядати дивно: якщо вас не цікавлять внутрішні номери Redis, вам не слід піклуватися про те, як внутрішні типи даних реалізуються. Це з простої причини: для кожної операції Redis ви знайдете часову складність у документації, а якщо у вас є набір операцій та часова складність, єдине інше, що вам потрібно, - це підказка про використання пам'яті (і тому ми робимо багато оптимізацій, які можуть залежати від даних, найкращий спосіб отримати ці останні цифри - це кілька тривіальних тестів реального світу).

Але оскільки ви запитували, ось основна реалізація кожного типу даних Redis.

  • Рядки реалізуються за допомогою бібліотеки динамічних рядків С, щоб ми не платили (асимптотично) за виділення в операціях додавання. Таким чином, ми маємо, наприклад, O (N), а не квадратичну поведінку.
  • Списки реалізуються за допомогою пов'язаних списків.
  • Набори і хеш реалізуються з допомогою хеш - таблиці.
  • Відсортовані набори реалізуються за допомогою пропускних списків (своєрідний тип збалансованих дерев).

Але коли списки, набори та відсортовані набори невеликі за кількістю елементів та розміром найбільших значень, застосовується інше, набагато більш компактне кодування. Це кодування відрізняється для різних типів, але має таку особливість, що це компактне блокування даних, яке часто змушує проводити O (N) сканування для кожної операції. Оскільки ми використовуємо цей формат лише для невеликих об'єктів, це не проблема; сканування невеликого O (N) крапки - це кеш-пам'ять, тому практично кажучи, це дуже швидко, і коли є занадто багато елементів, кодування автоматично перемикається на нативне кодування (зв'язаний список, хеш тощо).

Але ваше запитання насправді не стосувалося лише внутрішніх справ, ваш пункт - який тип використовувати для досягнення чого? .

Струни

Це базовий тип усіх типів. Це один з чотирьох типів, але також є базовим типом складних типів, оскільки Список - це список рядків, Набір - це набір рядків тощо.

Рядок Redis - це гарна ідея у всіх очевидних сценаріях, де ви хочете зберігати HTML-сторінку, але також і тоді, коли ви хочете уникнути перетворення вже закодованих даних. Так, наприклад, якщо у вас JSON або MessagePack, ви можете просто зберігати об'єкти як рядки. У Redis 2.6 ви навіть можете маніпулювати цим типом об’єктного сервера за допомогою скриптів Lua.

Ще одне цікаве використання рядків - це растрові карти та загальні масиви байтів з випадковим доступом, оскільки Redis експортує команди для доступу до випадкових діапазонів байтів або навіть одиничних біт. Наприклад, перевірте цю хорошу публікацію в блозі: Швидкі прості показники в режимі реального часу за допомогою Redis .

Списки

Списки хороші, коли ви, ймовірно, торкаєтесь лише крайніх списків: біля хвоста чи біля голови. Списки не дуже хороші для пагінування матеріалів, оскільки випадковий доступ повільний, O (N). Тож гарне використання списків - це звичайні черги та стеки, або обробка елементів у циклі, використовуючи RPOPLPUSH з тим самим джерелом та призначенням, щоб "обертати" кільце елементів.

Списки також хороші, коли ми хочемо просто створити обмежену колекцію з N елементів, де зазвичай ми отримуємо доступ лише до верхніх або нижніх елементів, або коли N невеликий.

Набори

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

Набори також добре представляти стосунки, наприклад, "Що таке друзі користувача X?" і так далі. Але інші хороші структури даних для подібних матеріалів - це сортовані набори, як ми побачимо.

Набори підтримують складні операції, такі як перетини, об'єднання тощо, тому це хороша структура даних для використання Redis "обчислювальним" способом, коли у вас є дані і ви хочете виконати перетворення на цих даних, щоб отримати деякий вихід.

Невеликі набори кодуються дуже ефективно.

Хеші

Хеші - це ідеальна структура даних для представлення об'єктів, що складається з полів і значень. Поля хешів також можуть бути атомним збільшенням за допомогою HINCRBY. Якщо у вас є такі об'єкти, як користувачі, дописи в блозі чи якийсь інший предмет , хеши - це, ймовірно, шлях, якщо ви не хочете використовувати власне кодування, наприклад JSON або подібне.

Однак майте на увазі, що маленькі хеші кодуються Redis дуже ефективно, і ви можете попросити Redis атомно GET, SET або збільшити окремі поля дуже швидко.

Хеші можуть також використовуватися для представлення пов'язаних структур даних, використовуючи посилання. Наприклад, перевірте реалізацію коментарів на lamernews.com.

Відсортовані набори

Відсортовані набори є єдиними іншими структурами даних, крім списків, для підтримки упорядкованих елементів . З відсортованими наборами ви можете зробити кілька класних речей. Наприклад, у вашій веб-програмі можуть бути всі види списків " Найпопулярніші" . Найкращі користувачі за рахунком, головні публікації за переглядом сторінок, найкращі версії, але один екземпляр Redis підтримуватиме тони вставки та операції отримання елементів-топ-елементів за секунду.

Відсортовані набори, як і звичайні набори, можуть використовуватися для опису відносин, але вони також дозволяють укласти список елементів і запам'ятати впорядкування. Наприклад, якщо я пам'ятаю друзів користувача X з відсортованим набором, я можу легко запам'ятати їх у порядку прийнятої дружби.

Відсортовані набори хороші для черг з пріоритетом.

Відсортовані набори схожі на більш потужні списки, де вставка, видалення або отримання діапазонів із середини списку завжди швидкі. Але вони використовують більше пам'яті і є O (log (N)) структурами даних.

Висновок

Я сподіваюся, що я надав трохи інформації в цій публікації, але набагато краще завантажити вихідний код lamernews з http://github.com/antirez/lamernews і зрозуміти, як це працює. Багато структур даних Redis використовуються всередині Lamer News, і є багато підказок про те, що використовувати для вирішення заданої задачі.

Вибачте за помилки граматики, тут півночі і занадто втомився, щоб переглянути публікацію;)


45
Це єдиний автор Редіса. Я надіслав його по електронній пошті і попросив відповісти. Дякую тобі дуже-дуже, Сальваторе. Це чудова інформація.
Гомер6

58
Дякую, але я не єдиний великий учасник, Пітер Нордхуїс надав дуже великі частини поточної реалізації :)
antirez

1
Якщо однаковий рядок є у багатьох різних наборах, чи зберігатиметься лише одна копія рядка?
sbrian

Яким є zscore в O (1), використовуючи лише пропускний список?
Максим

1
Хоча список пропусків не є належним збалансованим деревом, ви можете бачити пропускний список як "перевернуте" випадкове дерево. Вони в основному своєрідні еквіваленти, навіть якщо реалізація та компонування відрізняються.
антирез

80

Здебільшого вам не потрібно розуміти основні структури даних, які використовує Redis. Але трохи знань допоможе вам зробити компромісні операції з пам'яті процесора в / с. Це також допомагає ефективно моделювати свої дані.

Внутрішньо Redis використовує такі структури даних:

  1. Рядок
  2. Словник
  3. Двозначно пов'язаний список
  4. Пропустити список
  5. Поштовий список
  6. Int Sets
  7. Карти Zip (застаріла на користь поштового списку з Redis 2.6)

Щоб знайти кодування, яке використовується певним ключем, використовуйте команду object encoding <key>.

1. Струни

У Redis рядки називають простими динамічними рядками або SDS . Це невелика обгортка над a, char *що дозволяє зберігати довжину рядка та кількість вільних байтів як префікс.

Оскільки довжина рядка зберігається, strlen є операцією O (1). Крім того, оскільки довжина відома, рядки Redis є бінарними безпечними. Цілком законно, щоб рядок містив нульовий символ .

Струни - це найбільш універсальна структура даних, доступна в Redis. Рядок - це все наступне:

  1. Рядок символів, який може зберігати текст. Див . Команди SET і GET .
  2. Байтовий масив, який може зберігати двійкові дані.
  3. long, Який може зберігати номери. Див INCR , ВЗЦЗ , INCRBY і DECRBY команди.
  4. Array (з chars, ints, longsабо будь-якого іншого типу даних) , що може дозволити ефективно випадковому доступ. Див. Команди SETRANGE та GETRANGE .
  5. Бітовий масив , який дозволяє встановити або отримати окремі біти. Див. Команди SETBIT та GETBIT .
  6. Блок пам'яті, який можна використовувати для побудови інших структур даних. Це використовується внутрішньо для складання ziplists та intsets, які є компактними, ефективними для пам’яті структурами даних для невеликої кількості елементів. Детальніше про це нижче.

2. Словник

Redis використовує словник для наступного:

  1. Щоб відобразити ключ до його пов’язаного значення, де значенням може бути рядок, хеш, набір, відсортований набір або список.
  2. Щоб відобразити ключ до його часової позначки.
  3. Для реалізації типів даних Hash, Set та сортування набору.
  4. Щоб зіставити команди Redis на функції, які обробляють ці команди.
  5. Щоб зіставити клавішу Redis до списку клієнтів, які заблоковані на цій клавіші. Див. BLPOP .

Словники Redis реалізовані за допомогою таблиць Hash . Замість пояснення реалізації я просто поясню конкретні речі Redis:

  1. Словники використовують структуру під назвою dictType для розширення поведінки хеш-таблиці. Ця структура має покажчики функцій, і тому такі операції можна розширити: а) хеш-функція, б) порівняння ключів, в) деструктор ключа та г) деструктор значення.
  2. Словники використовують мурмурш2 . (Раніше вони використовували хеш-функцію djb2 , з seed = 5381, але потім хеш-функцію було переведено на murmur2 . Див. Це питання для пояснення алгоритму хешу djb2 .)
  3. Redis використовує інкрементальний хешинг, також відомий як Incremental Resizing . У словнику є дві хеш-таблиці. Щоразу, коли словник торкається , одне відро переміщується з першої (меншої) хеш-таблиці на другу. Таким чином, Redis запобігає дорогій операції зміни розміру.

Структура Setданих використовує словник, щоб гарантувати відсутність дублікатів. Sorted SetВикористовує словник для відображення елемента в його рахунок, тому ZSCORE є O (1) операції.

3. Подвійно пов'язані списки

Тип listданих реалізується за допомогою списків, що мають зв'язані між собою . Реалізація Redis - це прямо з алгоритму-підручника. Єдина зміна полягає в тому, що Redis зберігає довжину в структурі даних списку. Це забезпечує LLEN має складність O (1).

4. Пропустити списки

Redis використовує пропуск списків як основу структури даних для сортованих наборів. У Вікіпедії є хороший вступ. У статті Вільяма П'ю Пропущені списки: ймовірнісна альтернатива збалансованим деревам має більше деталей.

Відсортовані набори використовують як пропускний список, так і словник. У словнику зберігається оцінка кожного елемента.

Реалізація пропуску списку Redis відрізняється від стандартної реалізації наступними способами:

  1. Redis дозволяє повторити бали. Якщо два вузли мають однаковий бал, їх сортують за лексикографічним порядком .
  2. Кожен вузол має зворотний покажчик на рівні 0. Це дозволяє переміщати елементи у зворотному порядку оцінки.

5. Поштовий список

Список Zip схожий на подвійно пов'язаний список, за винятком того, що він не використовує покажчики та зберігає дані вбудовані.

Кожен вузол у подвійно пов'язаному списку має 3 вказівника - один вказівник вперед, один вказівник назад і один вказівник для посилання на дані, що зберігаються в цьому вузлі. Покажчики потребують пам'яті (8 байт у 64-бітовій системі), і тому для невеликих списків подвійно пов'язаний список дуже неефективний.

Список Zip зберігає елементи послідовно в рядку Redis. Кожен елемент має невеликий заголовок, який зберігає довжину та тип даних елемента, зміщення до наступного елемента та зміщення до попереднього елемента. Ці зміщення замінюють вказівники вперед і назад. Оскільки дані зберігаються вбудовані, нам не потрібен покажчик даних.

Список Zip використовується для зберігання невеликих списків, відсортованих наборів та хешей. Відсортовані набори згладжуються у такий список, як [element1, score1, element2, score2, element3, score3]і зберігаються у Zip List. Хеші сплющуються в список, як [key1, value1, key2, value2]і т.д.

Завдяки Zip Lists ви маєте право здійснювати компроміс між процесором та пам'яттю. Списки Zip є ефективними для пам'яті, але вони використовують більше процесора, ніж пов'язаний список (або таблиця Hash / Skip List). Пошук елемента в zip-списку - це O (n). Вставка нового елемента вимагає перерозподілу пам'яті. Через це Redis використовує це кодування лише для невеликих списків, хешей та відсортованих наборів. Ви можете налаштувати цю поведінку, змінивши значення <datatype>-max-ziplist-entriesта <datatype>-max-ziplist-value>в redis.conf. Для отримання додаткової інформації див. Оптимізацію пам'яті Redis, розділ "Спеціальне кодування малих сукупних типів даних" для отримання додаткової інформації.

У коментарі на ziplist.c чудовий, і ви можете зрозуміти цю структуру даних повністю без необхідності читати код.

6. Внутрішні набори

Int Sets - фантастична назва для "Сортовані масиви цілих чисел".

У Redis набори реалізуються зазвичай за допомогою хеш-таблиць. Для невеликих наборів хеш-таблиця неефективна. Коли набір складається лише з цілих чисел, масив часто є більш ефективним.

Int Set - це відсортований масив цілих чисел. Для пошуку елемента використовується двійковий алгоритм пошуку. Це має складність O (log N). Додавання нових цілих чисел до цього масиву може потребувати перерозподілу пам'яті, що може стати дорогим для великих цілих масивів.

Для подальшої оптимізації пам’яті Int Sets поставляється в 3 варіантах з різними цілими розмірами: 16 біт, 32 біти і 64 біт. Redis досить розумний, щоб використовувати правильний варіант залежно від розміру елементів. Коли новий елемент додається і він перевищує поточний розмір, Redis автоматично переносить його на наступний розмір. Якщо рядок додано, Redis автоматично перетворює Int Set у звичайний набір таблиці Hash Table.

Int Sets - це компроміс між процесором та пам'яттю. Int Sets надзвичайно ефективні в пам'яті, а для невеликих наборів вони швидші, ніж хеш-таблиця. Але після певної кількості елементів час пошуку для O (log N) та вартість перерозподілу пам'яті стають занадто великими. На основі експериментів було встановлено, що оптимальний поріг для переходу на звичайну хеш-таблицю становить 512. Однак ви можете збільшити цей поріг (зменшувати його не має сенсу), виходячи з потреб вашого додатка. Дивіться set-max-intset-entriesу redis.conf.

7. Zip Maps

Карти Zip - це словники, вирівняні та зберігаються у списку. Вони дуже схожі на Zip Lists.

Карти Zip застаріли з Redis 2.6, а невеликі хеші зберігаються в Zip Lists. Щоб дізнатися більше про це кодування, зверніться до коментарів на zipmap.c .


2

Redis зберігає ключі, що вказують на значення. Клавішами може бути будь-яке двійкове значення до розумного розміру (для читання та налагодження рекомендується використовувати короткі ASCII рядки). Значення є одним із п’яти типових типів даних Redis.

1. рядки - послідовність бінарних безпечних байтів до 512 Мб

2.hashes - сукупність пар ключових значень

3.lists - список колекцій рядків у порядку вставки

4.sets - колекція унікальних рядків без впорядкування

5. набір рядків - це сукупність унікальних рядків, упорядкованих за бажанням, визначеним користувачем

Струни

Рядок Redis - це послідовність байтів.

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

Струни - це канонічна концепція "зберігання ключових цінностей". У вас є ключ, який вказує на значення, де і ключ, і значення є текстовими або двійковими рядками.

Про всі можливі операції над рядками див. Http://redis.io/commands/#string

Хеші

Хеш Redis - це сукупність пар ключових значень.

Хеш Redis містить багато пар ключових значень, де кожен ключ і значення є рядком. Redis хеші не підтримують складні значення безпосередньо (тобто, у хеш-полі не може бути значення списку чи набору чи іншого хеша), але ви можете використовувати хеш-поля для вказівки на інші комплексні значення верхнього рівня. Єдина спеціальна операція, яку ви можете виконати на значеннях хеш-поля, - приріст / зменшення атомного числового вмісту.

Хеші Redis можна думати двома способами: як пряме представлення об'єкта і як спосіб компактного зберігання багатьох малих значень.

Безпосередні уявлення об'єктів зрозумілі просто. Об'єкти мають ім'я (ключ хеша) та набір внутрішніх ключів зі значеннями. Дивіться приклад нижче для, ну, приклад.

Зберігання багатьох малих значень за допомогою хеша - це розумна техніка масового зберігання даних Redis. Якщо хеш має невелику кількість полів (~ 100), Redis оптимізує ефективність зберігання та доступу до всього хешу. Невелика оптимізація хеш-пам'яті Redis викликає цікаву поведінку: ефективніше мати 100 хешів із 100 внутрішніми ключами та значеннями, а не мати 10 000 клавіш верхнього рівня, що вказують на рядкові значення. Використання хедів Redis для оптимізації зберігання даних таким чином вимагає додаткових накладних програм програмування для відстеження того, де дані закінчуються, але якщо ваше зберігання даних, головним чином, базується на рядках, ви можете зберегти багато накладних витрат пам'яті, використовуючи цей дивний фокус.

Про всі можливі операції над хешами див. У хеш-документах

Списки

Списки Redis діють як пов'язані списки.

Ви можете вставляти в списки, видаляти з них та переміщувати їх з головою чи хвостом списку.

Використовуйте списки, коли потрібно підтримувати значення у тому порядку, в який вони були вставлені. (Redis дає вам можливість вставити в будь-яку довільну позицію списку, якщо вам потрібно, але ефективність вставки знизиться, якщо ви вставите далеко від початкової позиції.)

Списки Redis часто використовуються як черги виробників / споживачів. Вставте елементи в список, а потім виведіть елементи зі списку. Що станеться, якщо ваші споживачі намагаються вийти зі списку без елементів? Ви можете попросити Redis дочекатися появи елемента і повернути його вам негайно, коли він буде доданий. Це перетворює Redis у чергу повідомлень / подій / роботи / завдання / системи повідомлень у реальному часі.

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

Ви також можете підтримувати списки фіксованої довжини (обмежені колекції), обрізаючи список до певного розміру після кожного вставки.

Про всі можливі операції зі списками див. Документи зі списками

Набори

Набори Redis - це, ну, набори.

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

Набори швидкі для перевірки членства, вставки та видалення членів у наборі.

Набори мають ефективні задані операції, як ви і очікували. Ви можете взяти з'єднання, перетин та різницю декількох множин одночасно. Результати можуть бути повернені абоненту або результати можуть бути збережені в новому наборі для подальшого використання.

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

Про всі можливі операції над наборами див. Документи про набори .

Відсортовані набори

Відсортовані набори Redis - це набори із впорядкованим користувачем упорядкуванням.

Для простоти відсортований набір можна думати як двійкове дерево з унікальними елементами. (Повторно відсортовані набори - це фактично пропуск списків .) Порядок сортування елементів визначається оцінкою кожного елемента.

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

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

Елементи додаються до вашого набору з балами. Ви можете оновити бал будь-якого елемента в будь-який час, просто знову додайте елемент з новою оцінкою. Оцінки представлені парними знаками з плаваючою комою, тому за потреби можна вказати деталізацію часових позначок високої точності. Кілька елементів можуть мати однаковий бал.

Ви можете отримати елементи кількома різними способами. Оскільки все відсортовано, ви можете запитати елементи, починаючи з найнижчих балів. Ви можете запитати елементи, починаючи з найвищих балів ("у зворотному порядку"). Ви можете запитувати елементи за їх сортуванням у натуральному чи зворотному порядку.

Про всі можливі операції над відсортованими наборами дивіться документи про відсортовані набори.

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