Какой канонический способ объединения строк в списке? - PullRequest
21 голосов
/ 12 января 2012

Я хочу конвертировать ("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)))))

Ответы [ 5 ]

41 голосов
/ 12 января 2012

Использовать ФОРМАТ .

~{ и ~} обозначают итерацию, ~A обозначает эстетическую печать, а ~^ (также известный как Tilde Circumflex в документах) обозначает печать, только когда что-то следует за ней.

* (format nil "~{~A~^, ~}" '( 1 2 3 4 ))

"1, 2, 3, 4"
* 
5 голосов
/ 12 декабря 2016

Это решение позволяет нам использовать 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. Поскольку мы не можем заставить Лисп вызывать функцию %d из FORMAT с аргументом разделителя, нам нужно получить его откуда-то еще - здесь из динамического связывания, введенного функцией join.

Единственная цель функции %d - написать разделитель - он игнорирует аргументы, переданные format - он использует только аргумент stream.

1 голос
/ 01 июня 2018

С новой и простой 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)))

(автор, чтобы сделать простые вещи, как это просто).

1 голос
/ 19 февраля 2016

Немного опоздал на вечеринку, но reduce отлично работает:

(reduce (lambda (acc x)
          (if (zerop (length acc))
              x
              (concatenate 'string acc "&" x)))
        (list "name=slappy" "friends=none" "eats=dogpoo")
        :initial-value "")
1 голос
/ 24 февраля 2012

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

(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)))))
...