Just Me
Just Me

Reputation: 41

extract a substring from a string in smalltalk (squeak)

I'm trying to extract a substring from a string which would be the substring in between 2 delimiters i.e it should be defined as follows:

substring: aString delimiter: aDelimiter

and, for an example, if i'll get this line:

substring: 'dddd#sss#dddd' delimiter: '#'

the function should return 'sss'.

this is what i've been trying, which didn't work:

substring: aString delimiter: aDelimiter
|index temp1 temp2 sz arr str|
      arr := aString asArray.
      sz := arr size.
      index := arr lastIndexOf: aDelimiter.
      temp1 := arr first: (sz - index +1).
      index := temp1 lastIndexOf: aDelimiter.
      sz :=temp1 size.
      temp2 := temp1 first: (sz - index).
      str := temp2 asString.
      ^str.

I don't know if it's worth mentioning but it's supposed to be a class method.

Upvotes: 4

Views: 4022

Answers (3)

Leandro Caniglia
Leandro Caniglia

Reputation: 14858

While I like both of the answers above, you might also want to consider another that is closer to your initial attempt and is a little bit more efficient than the others in that it only creates the object you are looking for (e.g., no intermediate Stream)

substringOf: aString delimitedBy: aCharacter
    | i j |
    i := aString indexOf: aCharacter.
    j := aString indexOf: aCharacter startingAt: i + 1.
    ^aString copyFrom: i + 1 to: j - 1

(Note b.t.w. that I'm also suggesting a slightly different selector.)

Another aspect you would like to consider is how the method should react if aCharacter is not in aString, it is only once or it has three or more occurrences. Something in the lines of:

substringOf: aString delimitedBy: aCharacter
    | i j |
    i := aString indexOf: aCharacter.
    i = 0 ifTrue: [^''].
    j := aString indexOf: aCharacter startingAt: i + 1.
    j = 0 ifTrue: [^''].
    ^aString copyFrom: i + 1 to: j - 1

But again, if performance is not a concern in your case, then go for the readStream upTo upTo answer, as it is probably the best.

Upvotes: 2

Sean DeNigris
Sean DeNigris

Reputation: 6390

You aren't far from working code, as David pointed out. But I'd just like to point out that it's very procedural. A lot of the magic of Smalltalk, and OOP in general, is writing beautiful, easy to understand code that sends intention revealing messages to a community of appropriate objects. This includes leaning on the objects already existing in the image. I can't think of a time when I've had to go this low level for a simple task like this. It would be great to read one of the many awesome OOP references. My favorite is A Mentoring Course on Smallalk

I think David's solution is right on. I personally like second instead of at: 2, but it feels picky and might be personal preference ('dddd#sss#dddd' subStrings: '#') second

Upvotes: 3

David Buck
David Buck

Reputation: 2847

Your basic problem is that the argument aDelimiter is a string instead of a character. You want to call it with $# instead of '#'.

Now for some easier ways. Probably the easiest is to use the subStrings: method:

('dddd#sss#dddd' subStrings: '#') at: 2

This has the disadvantage that it extracts the entire string into substrings separated by the # character which may be more than you need.

The next easiest option is to use streams:

'dddd#sss#dddd' readStream upTo: $#; upTo: $#

That code only extracts the part that you need.

Upvotes: 4

Related Questions