views:

1787

answers:

4

I have a query request-uri in the form of "/node/143" (just an example of the format).

I want to strip the first forward slash from the string, I looked up the function remove and had a try. I just can't seem to get it working (I'm using SBCL on Linux).

I've set the request-uri using this code.

(setq request-uri "/node/143")

When I check the variable I have this returned.

request-uri
"/node/143"

I now try to remove the first slash (at this point it's just anything at all to see how the function is properly used).

(remove "/" request-uri)
"/node/143"

(remove '/ request-uri)
"/node/143"

I even tried supplying a list

(remove '("/") request-uri)
"/node/143"

(remove '('/) request-uri)
"/node/143"

Even though strings are vectors of characters I thought that somehow maybe the whole string may be placed in one cell and I tried to remove the whole thing, still no luck.

(remove "/node/143" request-uri)
"/node/143"

(remove '/node143 request-uri)
"/node/143"

So I'm at a loss right now, this seemingly simple function has really eluded me, I thought I followed the documentation to the letter, but nothing is working.

Can anyone shed some light on what's happening here?

Thanks.

Edit: I found the answer to my question, which raised another question.

To remove an element from the string I used

(remove #\/ request-uri)

What about a whole string

`(remove #\node request-uri`)

Only works for the first character and throws an error, and the following all do nothing.

(remove "node" request-uri)
(remove 'node request-uri)
(remove ?\node request-uri)
(remove #\node request-uri)
(remove '("node") request-uri)

I'm not sure how else it should be addressed here.

A: 

This fails because "/" is a string, not a character.

(remove "/" request-uri)
"/node/143"

If you want the character, you need to use #\/ i.e. the forward slash character.

(remove #\/ request-uri)
"node143"

But as you can see, it'll remove all forward slashes. According to doc page you linked, remove takes a keyword param named :count, which says how many items to remove. So you could do this.

(remove #\/ request-uri :count 1)
"node/143"

Hope this helps.

::EDIT::

It's not ?\/, it's #\/. Thanks Douglas!

Tung Nguyen
Copied your code exactly as appeared, got this error: The variable ?/ is unbound.#\/ works though, as I edited above.
Douglas Brunner
?\/ is no character. Character is #\/ .
Rainer Joswig
Ah yes, I'll fix it right away. Haven't used CL in a while.
Tung Nguyen
+1  A: 
Svante
+2  A: 

Explanation:

(remove "node" request-uri)
(remove 'node request-uri)
(remove ?\node request-uri)
(remove #\node request-uri)
(remove '("node") request-uri)

These are (respectively): a string, a (quoted) symbol, an (evaluated) symbol, a malformed character literal, and a list containing one string. REMOVE removes an object from a sequence-of-object, and a string is not a sequence of any of these things.

If you just want to remove part of the string, SUBSEQ might do the trick:

(let ((uri "/node/143"))
  (when (string= (subseq uri 0 6) "/node/")
    (format t "user wants node ~D" (parse-integer (subseq uri 6)))))

For more complex things, you probably want a library like cl-ppcre and/or split-sequence. (If you're using Hunchentoot, you already have the former loaded.)

Anonymous Coward
+4  A: 

Learn to read the Common Lisp HyperSpec.

Strings are Sequences, Arrays (one-dimensional vectors of characters) and, well, Strings. This means that most of those functions are applicable.

Let's look at REMOVE. CLHS gives this signature:

remove item sequence &key from-end test test-not start end count key
    => result-sequence

Strings are sequences of characters. So a call to remove would be:

(remove #\/ "/foo/")

or (for example)

(remove #\/ "/foo/" :start 2)

Remember: #\a is a character. #\node is no character. Illegal. A string is "/foo/".

The item to REMOVE from a string has to be a character. Nothing else. Why? Because the TEST is by default EQL and EQL compares the character in the string with your item argument. Also key is by default IDENTITY and does not change the items.

What if your argument is a string? Well, then you have to do more:

 (remove "/" "/abc/" :key #'string :test #'equal)

This looks at each character of the sequence and makes it into a string. The string then will be compared with your item "/" using the function EQUAL. This works also. The cost is that it needs to generate a string from each character of "/abc/", each string is a new object.

Another way to do it is:

 (remove "/" "/abc/" :test (lambda (a b) (eql (aref a 0) b)))

Above retrieves the first character of "/" in every test and compares it with the character from the string "/abc/". Again the cost is that it needs to get the character five times (in this example).

So the best way to write it if your original object comes as a string:

(remove (aref "/" 0) "/abc/")

Above we get the character from the string "/" once and then REMOVE compares this character with the default EQL test with each character in the string - it returns a new string of those characters that are not EQL to #\/.

What do you expect ?\foo to be? In Common Lisp this is the symbol |?fOO|.

Also (remove "foo" "afoob") does not work since a string ("foo" here) is not an element of a string. Remember, characters are elements of strings. Remember also that strings with one item like "/" are still strings and not a character. Thus "/" and #\/ are of different type. The first is a string and the second is a character.

SUBSEQ extracts a sequence from a sequence. That means it also extracts a string from another string:

(subseq "0123456" 1 5)
     where 1 is the start and 5 is the end index.

CONCATENATE appends sequences. This means that it also appends strings.

(concatenate 'string "abc" "123")
  returns a new string with the strings "abc" and "123" appended.

To remove parts of a string see also the functions STRING-TRIM, STRING-LEFT-TRIM and STRING-RIGHT-TRIM.

So, as in one other answer to remove substrings from a string you need to write some code to extract some strings and then concatenate those.

SEARCH searches for strings in strings.

Rainer Joswig
Read my post more carefully, I did read a manual, a well formatted one at that. http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node144.html
Douglas Brunner
Do not use CLtL2 as the first source. It is outdated. The HyperSpec is based on the ANSI Standard. CLtL2 is not. CLtL2 describes some language version during the standardisation process before the work was finished.
Rainer Joswig
Good tip, thanks.
Douglas Brunner