Каков канонический способ объединения строк в список?
Я хочу преобразовать ("USERID=XYZ" "USERPWD=123")
в "USERID=XYZ&USERPWD=123"
. Я попробовал
(apply #'concatenate 'string '("USERID=XYZ" "USERPWD=123"))
который вернет ""USERID=XYZUSERPWD=123"
.
Но я не знаю, как вставить '&'? Следующая функция работает, но кажется немного сложной.
(defun join (list &optional (delim "&"))
(with-output-to-string (s)
(when list
(format s "~A" (first list))
(dolist (element (rest list))
(format s "~A~A" delim element)))))
Ответы
Ответ 1
Используйте FORMAT.
~{
и ~}
обозначают итерацию, ~A
обозначает эстетическую печать, а ~^
(или Tilde Circumflex в документах) обозначает печать, только когда что-то следует за ней.
* (format nil "~{~A~^, ~}" '( 1 2 3 4 ))
"1, 2, 3, 4"
*
Ответ 2
Это решение позволяет нам использовать FORMAT
для создания строки и иметь переменный разделитель. Цель состоит не в том, чтобы создать новую строку формата для каждого вызова этой функции. Хороший компилятор Common Lisp также может захотеть скомпилировать заданную строку с фиксированным форматом, которая не работает, когда строка формата создается во время выполнения. Смотрите макрос formatter
.
(defun %d (stream &rest args)
"internal function, writing the dynamic value of the variable
DELIM to the output STREAM. To be called from inside JOIN."
(declare (ignore args)
(special delim))
(princ delim stream))
(defun join (list delim)
"creates a string, with the elements of list printed and each
element separated by DELIM"
(declare (special delim))
(format nil "~{~a~^~/%d/~:*~}" list))
Объяснение:
"~{ iteration start
~a print element
~^ exit iteration if no more elements
~/%d/ call function %d with one element
~:* move one element backwards
~}" end of iteration command
%d
- это просто "внутренняя" функция, которая не должна вызываться извне join
. В качестве маркера для этого у него есть префикс %
.
~/foo/
- это способ вызова функции foo
из строки формата.
Переменные delim
объявлены специальными, так что может быть значение для разделителя, переданного в функцию %d
. Поскольку мы не можем заставить Lisp вызывать функцию %d
из FORMAT
с аргументом разделителя, нам нужно получить его откуда-то еще - здесь из динамического связывания, введенного функцией join
.
Единственная цель функции %d
- написать разделитель - он игнорирует аргументы, переданные format
- он использует только аргумент stream
.
Ответ 3
Предполагая список строк и один разделитель символов, следующее должно работать эффективно для частых вызовов в коротких списках:
(defun join (list &optional (delimiter #\&))
(with-output-to-string (stream)
(join-to-stream stream list delimiter)))
(defun join-to-stream (stream list &optional (delimiter #\&))
(destructuring-bind (&optional first &rest rest) list
(when first
(write-string first stream)
(when rest
(write-char delimiter stream)
(join-to-stream stream rest delimiter)))))
Ответ 4
Немного поздно для вечеринки, но reduce
отлично работает:
(reduce (lambda (acc x)
(if (zerop (length acc))
x
(concatenate 'string acc "&" x)))
(list "name=slappy" "friends=none" "eats=dogpoo")
:initial-value "")
Ответ 5
С новой и простой библиотекой str :
(ql:quickload "str")
(str:join "&" '("USERID=XYZ" "USERPWD=123"))
Использует формат, как объяснено в других ответах:
(defun join (separator strings)
" "
(let ((separator (replace-all "~" "~~" separator)))
(format nil
(concatenate 'string "~{~a~^" separator "~}")
strings)))
(автор этого, чтобы сделать простые вещи, как это просто).
Ответ 6
Melpa содержит пакет s ("Библиотека давно потерянных манипуляций со строками Emacs"), которая предоставляет множество простых утилит для работы со строками, в том числе присоединение к строке:
(require 's)
(s-join ", " (list "a" "b" "c"))
"a, b, c"
Для чего он стоит, очень тонко под капотом, он использует пару функций LISP из fns.c
, а именно mapconcat
и identity
:
(mapconcat 'identity (list "a" "b" "c") ", ")
"a, b, c"