I need some help Splitting strings and checking is a string is a number

I’m trying to create a macro to pad all the track numbers in the Track Names with 0 (track 9 to track 09)

Here is what i have:

(let ((tracks (aud-get-info "tracks")))
  (dotimes (j (length tracks))
    (setf track (nth j tracks))
    (setf TrackName (second (assoc 'name track)))
    (setf NewTrackName "")
; Need to split TrackName on " " into list my-list
    (dolist (str my-list)
; Need to check if str is a number
; If it is format it as a zero padded string of 2 characters ie 09 into fStr
; Else set fStr = str
      (setf tempTrackName (format nil "~a ~a" NewTrackName fStr))
      (setf NewTrackName tempTrackName))

    (aud-do-command "SelectTracks" :track j :trackcount 1)
    (aud-do-command "SetTrackStatus:" :name (format nil "~a" NewTrackName))))

I reformatted your code and added code tags (the “</>” button) to make it more readable.

Splitting a string into a list:

(setf txt "The quick brown fox")

(defun string-to-list (str)
  (let ((word "")
        (results ()))
    (dotimes (i (length str) (reverse results))
      (setf ch (char str i))
      (cond ((char= ch #\space) (push word results)
                                (setf word ""))
            (t (setf word (format nil "~a~a" word ch)))))))

(string-to-list txt)
; returns list ("The" "quick" "brown" "fox") 

To test if a string is a number:

(numberp (eval-string "42"))  ; returns true

Thank you for you help trouble with the Testing for a number, I suspect the issue is with my if statement.
Can someone please point out what I’m doing wrong?

Thank you,

Richard

(defun string-to-list (str)
(let ((word "")
     (results ()))
 (setf str (format nil "~a " str)) ;So it gets the last word   
 (dotimes (i (length str) (reverse results))
   (setf ch (char str i))
   (cond ((char= ch #\space) (push word results)
                             (setf word ""))
         (t (setf word (format nil "~a~a" word ch)))))))


(let ((tracks (aud-get-info "tracks")))
 (dotimes (j (length tracks))
    (setf track (nth j tracks)) 
    (setf TrackName (second (assoc 'name track)))
    (setf NewTrackName "")    
    (setf my-list (string-to-list TrackName)) 
    (dolist (str my-list)

        (if ((numberp (eval-string  str))
            (setf fStr (format nil "~a" (subseq (format nil "0000~a" str) (sum (length str) 2))))
            (setf fStr str)))

        (setf tempTrackName (format nil "~a ~a" NewTrackName fStr))
        (setf NewTrackName tempTrackName)
    )              
    ;;(aud-do-command "SelectTracks" :track j :trackcount 1)
    ;;(aud-do-command "SetTrackStatus:" :name (format nil "~a" NewTrackName)) 
    (format t "~a~%" NewTrackName)       
 ) 
)    

Got it

Here is the working code.

Thanks for the help,

Richard

(defun string-to-list (str)
  (let ((word "")
        (results ()))
    (setf str (format nil "~a " str)) ;So it gets the last word   
    (dotimes (i (length str) (reverse results))
      (setf ch (char str i))
      (cond ((char= ch #\space) (push word results)
                                (setf word ""))
            (t (setf word (format nil "~a~a" word ch)))))))


(let ((tracks (aud-get-info "tracks")))
    (dotimes (j (length tracks))
       (setf track (nth j tracks)) 
       (setf TrackName (second (assoc 'name track)))
       (setf NewTrackName "")    
       (setf my-list (string-to-list TrackName)) 
       (dolist (str my-list)
            (if (numberp (string-to-number  str))
               (if (< (length str) 2)
                   (setf fStr (format nil "~a" (subseq (format nil "0000~a" str) (sum (length str) 2))))
                   (setf fStr str)  )
                (setf fStr str)  )
           (setf tempTrackName (format nil "~a ~a" NewTrackName fStr))
           (setf NewTrackName tempTrackName)
       )              
      (aud-do-command "SelectTracks" :track j :trackcount 1)
      (aud-do-command "SetTrackStatus:" :name (format nil "~a" NewTrackName)) 
 
    ) 
)    


What do you want to happen if the track name contains a 3 digit number?

Tip:
I’d suggest breaking this long line down into a separate function:

(setf fStr (format nil "~a" (subseq (format nil "0000~a" str) (sum (length str) 2))))

For example, if you want to pad integer numbers to a minimum number of digits, you could do something like:

(defun pad-zeros (str num)
  ;; Return integer padded with zeros to a minimum of'num' digits.
  ;; Return value as a string.
  (cond ((integerp str) (setf str (format nil "~a" str)))
        ((not (stringp str))
          (error "First argument must be an integer, or integer as a string.")))
  (let ((ln (length str)))
    (if (>= ln num)
        str
        (dotimes (i (- num ln) str)
          (setf str (strcat "0" str))))))

and then call the function (in this case, padding to 2 digits):

(pad-zeros number-string 2)

The goal was to zero pad the track numbers so I can sort them correctly before aligning them end to end and mixing them down to a single track. Since I don’t see myself ever working with 100 Plus tracks I never need to deal with 3 or more positions. Some track names have numbers other than the track numbers, if they are 1 digit numbers the zero padding doesn’t heart anything but if they are longer I didn’t want them getting truncated to 2 digits so I added the if (< (length str) 2) condition to ignore anything longer than 1 digit.

Thanks for the help,

Richard

That makes sense, and is what I guessed you were doing.

Fair enough.
In that case, I’d probably just use a macro to pad single digit numbers. For example:

(defmacro pad-single-digit(num)
  ;; Pad single digit number string with preceding zero
  `(if (= (length ,num) 1)
       (setf ,num (format nil "0~a" ,num))))

Example usage:

(setf a "3")
(pad-single-digit a)
(print a)  ; prints "03"

(Note: the first line in the macro is preceded with a backtick character, not a single quote character)

Your main loop could be written a bit more concisely.

  • You don’t really need intermediary temporary variables
  • The format function is not required in the final line - just use the string variable directly.
;type tool

(defun string-to-list (str)
  (let ((word "") results)
    (setf str (format nil "~a " str)) ;So it gets the last word
    (dotimes (i (length str) (reverse results))
      (setf ch (char str i))
      (cond ((char= ch #\space) (push word results)
                                (setf word ""))
            (t (setf word (format nil "~a~a" word ch)))))))


(let ((tracks (aud-get-info "tracks")))
  (dotimes (idx (length tracks) "")
    (setf track (nth idx tracks))
    (setf trk-name (second (assoc 'name track)))
    (setf new-name "")
    (setf my-list (string-to-list trk-name))
    (dolist (str my-list)
      (when (and (numberp (string-to-number  str)) (< (length str) 2))
        (setf str (format nil "~a" (subseq (format nil "0000~a" str) (sum (length str) 2)))))
      (setf new-name (format nil "~a ~a" new-name str)))
    (aud-do-command "SelectTracks" :track idx :trackcount 1)
    (aud-do-command "SetTrackStatus:" :name new-name)))

Maybe also worth commenting that aud-do-command "SetTrackStatus:" strips leading spaces in the new name. This seems to be an undocumented “feature”, so it shouldn’t really be relied on. If it were not for this feature, the above code would add a space at the start of each track name.

Here’s an alternative solution.
It’s a bit longer, mostly because it does not rely on (aud-do-command "SetTrackStatus:") to strip the space before the first word (see (join-words ...)).

A feature in this code that I thought you might find interesting is the line:

(let ((str-lst (get-output-stream-list (make-string-input-stream txt))))

This makes an input stream from the txt string, then reads the stream back as a list (of characters). We can then iterate through the list rather than reading the string character by character.

;type tool

(defun update-name (txt &aux (word "") (new-name ""))
  (let ((str-lst (get-output-stream-list (make-string-input-stream txt))))
    (setf str-lst (append str-lst '(#\space)))
    (dolist (ch str-lst new-name)
      (cond ((char= ch #\space)
                (pad-single-digit word)
                (setf new-name (join-words new-name word))
                (setf word ""))
            (t  (setf word (format nil "~a~a" word ch)))))))


(defun join-words(txt1 txt2)
  (if txt1  ; If txt1 is not NIL
      (format nil "~a ~a" txt1 txt2)
      txt2))


(defmacro pad-single-digit(str)
  ;; Pad single digit number string with preceding zero
  `(if (and (numberp (string-to-number ,str))
            (= (length ,str) 1))
       (setf ,str (format nil "0~a" ,str))))


(let ((trk-num 0))
  (dolist (track (aud-get-info "tracks") "")
    (setf trk-name (second (assoc 'name track)))
    (setf trk-name (update-name trk-name))
    (aud-do-command "SelectTracks" :track trk-num :trackcount 1)
    (aud-do-command "SetTrackStatus:" :name trk-name)
    (incf trk-num)))