Таблицы

Sergey ShishkinSergey Shishkin
4 min read

Есть функция tab - (tab 'lst 'any ..) -> NIL , которая выводит все аргументы в табличном формате. lst должен быть списком чисел, указывающим ширину поля для каждого аргумента. Все элементы в столбце будут выровнены по левому краю для отрицательных чисел, в противном случае — по правому краю. Имеются функции выравнивания, центрирования и переноса - align, center и wrap соответственно.

: (let Fmt (-3 14 14)
   (tab Fmt "Key" "Rand 1" "Rand 2")
   (tab Fmt "---" "------" "------")
   (for C '(A B C D E F)
      (tab Fmt C (rand) (rand)) ) )
Key        Rand 1        Rand 2
---        ------        ------
A               0    1481765933
B     -1062105905    -877267386
C      -956092119     812669700
D       553475508   -1702133896
E      1344887256   -1417066392
F      1812158119   -1999783937
-> NIL

И есть целая статья, посвященная работе с таблицами в PicoLisp.

(setq *People 
      '(((name . John) (phone . 123456) (age . 56))
        ((name . Fred) (phone . 654321) (age . 35))
        ((name . Fred) (phone . 236597) (age . 38))
        ((name . Hank) (phone . 078965) (age . 23))))

То, что вы видите, это способ указания пар, каждая пара — это CAR и CDR. C помощью assoc … к каждому подсписку можно получить доступ — поскольку каждый ключ — это CAR Имея это в виду, решение для извлечения человека по ключу и значению может выглядеть следующим образом:

(de assoc2d (Lst K V)
    (filter '((Sub)
              (let CurValue (cdr (assoc K Sub))
                (= V CurValue))) Lst ) )

(println (assoc2d *People 'name 'Fred))

В приведенном выше примере будет получен новый список со всеми лицами по имени Fred. Это работает через filter , который является членом того же семейства, что и mapcar . filter вернет новый список со всеми элементами, для которых функция обратного вызова/литерала вернула значение true. Sub будет каждым элементом, в данном случае каждым лицом.

Далее инициируется временная переменная (let CurValue (cdr (assoc K Sub) , используя логику CurValue, let является двоюродным братом setq, но создаст свое собственное небольшое пространство. Если бы у нас уже была переменная с именем CurValue с каким-либо значением в приведенном выше примере, эта переменная получила бы новое значение внутри выражения let. Однако после того, как выражение let завершит выполнение, CurValue вернется к своему исходному значению. Это удобный способ использовать имя переменной временно, не беспокоясь о том, что что-то еще испортится.

Далее мы используем функцию equal (=), чтобы проверить, равно ли наше текущее значение переданному значению в V, поскольку = вернет T (эквивалент Pico Lisp true), если они равны, или NIL (эквивалент false), и все готово. Все подсписки без нужной комбинации ключ/значение вернут NIL и, следовательно, не получат места в возвращаемом массиве.

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

(setq *People 
      '((name John phone 123456 age 56)
        (name Fred phone 654321 age 35)
        (name Fred phone 236597 age 38)
        (name Hank phone 078965 age 23)))

Нет проблем, тогда вы могли бы определить логику поиска следующим образом:

(de assoc1d (Lst Key) 
    (loop
     (NIL Lst NIL)
     (T (= Key (pop 'Lst)) (pop 'Lst))
     (pop 'Lst)))

(de assoc2d (Lst K V)
    (filter '((Sub)
              (= V (assoc1d Sub K))) Lst ) )

(println (assoc2d *People 'name 'Fred))

Наша пользовательская функция assoc1d по сути является заменой assoc, адаптированной под нашу пользовательскую систему таблиц. Она вернет значение переданного ключа или NIL, если ключ не может быть найден. Вы можете попробовать ее изолированно:

(println (assoc1d '(phone 123456 name John age 56) 'name))

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

Далее автор статьи демонстрирует механизм сортировки, сравнивая с PHP, а я предлагаю сравнить с Ryelang, где таблицы декларируются, в принципе, встроенный типом.

“При рассмотрении проблем в Lisp полезно думать в терминах того, как генерировать промежуточные списки, необходимые для решения проблемы … Основная идея … заключается в том, что мы сравниваем отсортированные имена с несортированными именами и возвращаем позицию исходного имени в отсортированном столбце … Обратите внимание также на использование name … это зарезервированное слово, но мы все равно можем его использовать, заключив в кавычки.”

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

0
Subscribe to my newsletter

Read articles from Sergey Shishkin directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sergey Shishkin
Sergey Shishkin

Всегда чему-то учусь!