LISP - Разделительная строка с разделителем также включена в новый список
У меня есть список следующих элементов
("(aviyon" "213" "flyingman" "no))") as list
То, что я хочу, это то, что я хочу разделить этот список, содержащий строки, используя скобки в качестве разделителя, но также хочу включить эти скобки в новый список, не нарушая порядок
Мой желаемый вывод нового списка (или тот же список изменен)
("(" "aviyon" "213" "flyingman" "no" ")" ")")
Я исхожу из императивных языков, и это будет 15-минутная работа на Java или C++. Но здесь я застрял, что делать. Я знаю, что должен
1- Получить элемент из списка в цикле
Я думаю, что это сделано с (nth 1 '(listname) )
2- разделить, не удаляя разделитель, добавить в новый список
Я нашел такие функции, как SPLIT-SEQUENCE, но я не могу обойтись без его удаления и без нарушения исходного порядка.
Любая помощь будет оценена.
3 ответа
Давайте еще один ответ, без внешних библиотек. Как вы уже сделали, мы можем разбить проблему на более мелкие части:
- определить функцию, которая создает список токенов из строки,
all-tokens
примените эту функцию ко всем строкам в вашем входном списке и объедините результат:
(mapcan #'all-tokens strings)
Первая часть, взяв состояние и составив из него список, выглядит как unfold
операция (анаморфизм).
Сложить (катаморфизм), называется reduce
в Лиспе строит значение из списка значений и функции (и, необязательно, начальное значение). Двойная операция, unfold
, принимает значение (состояние), функцию и генерирует список значений. В случае unfold
, функция шага принимает состояние и возвращает новое состояние вместе с результирующим списком.
Здесь давайте определим состояние как 3 значения: строку, начальную позицию в строке и стек проанализированных токенов. Наша функция шага next-token
возвращает следующее состояние.
;; definition follows below
(declare (ftype function next-token))
Основная функция, которая получает все токены из строки, просто вычисляет точку фиксации:
(defun all-tokens (string)
(do (;; initial start value is 0
(start 0)
;; initial token stack is nil
(tokens))
;; loop until start is nil, then return the reverse of tokens
((not start) (nreverse tokens))
;; advance state
(multiple-value-setq (string start tokens)
(next-token string start tokens))))
Нам нужна вспомогательная функция:
(defun parenthesisp (c)
(find c "()"))
Шаговая функция определяется следующим образом:
(defun next-token (string start token-stack)
(let ((search (position-if #'parenthesisp string :start start)))
(typecase search
(number
;; token from start to parenthesis
(when (> search start)
(push (subseq string start search) token-stack))
;; parenthesis
(push (subseq string search (1+ search)) token-stack)
;; next state
(values string (1+ search) token-stack))
(null
;; token from start to end of string
(when (< start (1- (length string)))
(push (subseq string start) token-stack))
;; next-state
(values string nil token-stack)))))
Вы можете попробовать с одной строкой:
(next-token "(aviyon" 0 nil)
"(aviyon"
1
("(")
Если вы возьмете полученные значения состояния и повторно их используете, у вас будет:
(next-token "(aviyon" 1 '("("))
"(aviyon"
NIL
("aviyon" "(")
И здесь второе возвращаемое значение - NIL, которое завершает процесс генерации. Наконец, вы можете сделать:
(mapcan #'all-tokens '("(aviyon" "213" "flyingman" "no))"))
Который дает:
("(" "aviyon" "213" "flyingman" "no" ")" ")")
Приведенный выше код не является полностью общим в том смысле, что all-tokens
слишком много знает о next-token
Вы могли бы переписать его, чтобы принять любое состояние. Вы также можете обрабатывать последовательности строк, используя тот же механизм, сохраняя больше информации в вашей переменной состояния. Кроме того, в реальном лексере вы бы не хотели переворачивать весь список токенов, вы бы использовали очередь для подачи синтаксического анализатора.
Вы можете использовать библиотеку cl-ppcre, чтобы сделать работу.
Например:
CL-USER> (ql:quickload :cl-ppcre)
CL-USER> (cl-ppcre:split "([\\(\\)])" "(aviyon" :with-registers-p t)
("" "(" "aviyon")
CL-USER> (cl-ppcre:split "([\\(\\)])" "no))" :with-registers-p t)
("no" ")" "" ")")
CL-USER>
Тем не менее, он делает пустые строки в списке. использование remove-if
Функция избавиться от них:
CL-USER> (defun empty-string-p (s) (string= s ""))
EMPTY-STRING-P
CL-USER> (remove-if 'empty-string-p
(list "no" ")" "" ")"))
("no" ")" ")")
Наконец, вы можете создать функцию, которая выполняет обе функции, и запустить ее в imperative
цикл (да, Common Lisp не работает, как многие думают):
CL-USER> (defun remove-empty-strings (l)
(remove-if 'empty-string-p l))
REMOVE-EMPTY-STRINGS
CL-USER> (defun split (s)
(cl-ppcre:split "([\\(\\)])"
s
:with-registers-p t))
SPLIT
CL-USER> (defparameter *the-list* '("(aviyon" "213" "flyingman" "no))"))
*THE-LIST*
CL-USER> (loop for item in *the-list*
for splitted = (split item)
for cleaned = (remove-empty-strings splitted)
append cleaned)
("(" "aviyon" "213" "flyingman" "no" ")" ")")
решение
Так как вы не поняли решение Александра, и так как я все равно написал свое решение:
;; load two essential libraries for any common lisper
(ql:quickload :cl-ppcre)
(ql:quickload :alexandria)
;; see below to see how to install quicklisp for `ql:quickload` command
;; it is kind of pythons `import` and if not install `pip install`
;; in one command for common-lisp
(defun remove-empty-string (string-list)
(remove-if #'(lambda (x) (string= x "")) string-list))
(defun split-parantheses-and-preserve-them (strings-list)
(remove-empty-string
(alexandria:flatten
(mapcar #'(lambda (el) (cl-ppcre:split "(\\(|\\))"
el
:with-registers-p t))
strings-list))))
;; so now your example
(defparameter *list* '("(aviyon" "213" "flyingman" "no))"))
(split-parantheses-and-preserve-them *list*)
;; returns:
;; ("(" "aviyon" "213" "flyingman" "no" ")" ")")
как это работает
(cl-ppcre:split "(\\(|\\))" a-string)
разбивает строку на (
или же )
, Потому что в регулярных выражениях (
или же )
используются для захвата матча - как и здесь (внешние паразиты захватывают) - вы должны избегать их. \\(
или же \\)
, Так с cl-ppcre:split
Вы можете разбить любую строку в общем lisp с помощью регулярных выражений. Супер крутой и супер эффективный пакет, написанный Эди Вейц. Он написал несколько очень сложных пакетов для общего lisp - их также называют ediware или edicls в сообществе. Кстати, cl-ppcre даже более эффективен и быстрее, чем золотой стандарт для регулярных выражений: движок Perl!
:with-regiesters-p t
Затем опция сохраняет согласованный разделитель, который должен быть заключен в круглые скобки, например: (<pattern>)
в шаблоне.
mapcar
это над списком, чтобы применить его к каждому строковому элементу в вашем списке строк.
Однако, что вы получили после этого, это список списков. (Каждый внутренний список, содержащий разделенный результат для каждого строкового элемента списка).
Свести список по alexandria:flatten
, Для многих функций, которые не соответствуют стандарту lisp, но которые, по вашему мнению, являются базовыми - например, сглаживание списка, - всегда смотрите сначала в Александрии - в основном это функция, которую вы хотите - это огромная библиотека. Вот почему он вам так или иначе нужен как обычный шалун;) .
Но все равно, вокруг будут пустые строки, которые будут удалены. Вот почему я написал remove-empty-string
который использует remove-if
- который вместе с remove-if-not
стандартная функция фильтрации списков Требуется функция предиката - здесь (lambda (x) (string= x ""))
который дает T, если строка является пустой строкой, и NIL, если нет. Он удаляет все элементы в результирующем сглаженном списке в нашей функции, которые являются пустыми строками. На других языках это будет называться filter
но да - иногда имена функций в Common-Lisp не очень хорошо подобраны. Иногда я думаю, что мы должны создать псевдонимы и перейти к ним и сохранить старые имена для обратной совместимости. У Clojure есть более приятные имена для функций... Может быть, cl люди должны обогнать имена функций clojure...
quicklisp
@ Александр Артеменко написал именно мое решение - он пришел первым. Я добавлю: если вы новичок в обычном lisp, возможно, вы не знаете, как использовать quicklisp. Делаем в терминале (linux или macos):
wget https://beta.quicklisp.org/quicklisp.lisp
В противном случае вручную загрузите в windows с адреса.
Я положил это в ~/quicklisp
папка.
Затем в clisp или sbcl сделайте:
(load "~/quicklisp/quicklisp.lisp") ;; just path to where downloaded
;; quicklisp.lisp file is!
;; then install quicklisp:
(quicklisp-quickstart:install)
;; then search for cl-ppcre
(ql:system-apropos "cl-ppcre")
;; then install cl-ppcre
(ql:quickload "cl-ppcre")
;; and to autoload everytime you start sbcl or clisp
;; in linux or mac - sorry I don't now windows that well
;; I have the opinion every programmer should us unix
;; as their OS
;; you have to let quicklisp be loaded when they start
;; by an entry into the init file
;; mostly located in ~/.sbclrc or ~/.clisprc.slip or such ...
;; respectively.
;; quicklisp does an entry automatically if you do:
(ql:add-to-init-file)
;; after installation do:
(quit)
;; If you then restart sbcl or clisp and try:
(ql:quickload :cl-ppcre)
;; it should work, - if not, you have to manually load
;; quicklisp first
(load "~/quicklisp/setup.lisp") ;; or wherever quicklisp's
;; setup.lisp file has been stored in your system!
;; and then you can do
(ql:quickload :cl-ppcre)
;; to install alexandria package then, do
(ql:quickload :alexandria) ;; or "alexandria"
;; ql:quickload installs the package from quicklisp repository,
;; if it cannot find package on your system.
;; learn more about quicklisp, since this is the package
;; manager of common lisp - like pip for python