Search code examples
lispcommon-lispextract

LISP extract an element from a list only in a specific case


The function i'm looking for has to return the index of the first , that is out of a pair of " ".

For example, with the sequence

{ " h i , a l l " : 3 , " h o w , i s " : " x " }

'( #\{ #\" #\h #\i #\, #\a #\l ... )

The function should return 11, not 4 (first occurrence of comma) because it is between " ".

I tried with this:

(defun control-comma (x p)
  (cond ((eql (car x) #\")
         (control-comma (subseq x (+ (position #\" x :start (+ 1 p)) 1)) p))
        ((eql (car x) #\,)
         p)
        (t
         (control-comma (cdr x) (+ 1 p)))
        )
  )

Using x as list of input and p as a 0-parameter to count the position, but it doesn't work and seems to be far away from the solution i'm looking for.

Thank you for every suggestion.


Solution

  • Instead of defining a complex function, I suggest you to use the predefined position-if operator:

    (defun first-comma (string start)
       (let ((in-double-quote nil))
         (position-if 
           (lambda (x)
             (case x
               ((#\") (progn (setf in-double-quote (not in-double-quote)) nil))
               ((#\,) (not in-double-quote))))
          string
          :start start)))
    
    CL-USER> (first-comma (coerce "{ \"hi, all\" : 3, \"how, is\" : \"x\" }" 'list) 0)
    15
    

    A more complex, recursive solution based again on the idea of scanning the input list one character at time, is given by the following function, where the state “inside double-quote” is encoded through a couple of recursive local functions:

    (defun fist-comma (x pos)
      (labels ((looking-for-comma (x pos)
                 (cond ((null x) nil)
                       ((eql (car x) #\,) pos)
                       ((eql (car x) #\") (looking-for-double-quote (cdr x) (1+ pos)))
                       (t (looking-for-comma (cdr x) (1+ pos)))))
               (looking-for-double-quote (x pos)
                 (cond ((null x) nil)
                       ((eql (car x) #\") (looking-for-comma (cdr x) (1+ pos)))
                       (t (looking-for-double-quote (cdr x) (1+ pos))))))
        (looking-for-comma (nthcdr pos x) pos)))
    

    Finally, note that in both the above functions one should take into account possible escaping of the double quote with appropriate means.