extract a substring from a string in smalltalk (squeak) - oop

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.

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.

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

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.

Related

VisualWorks - Find characters in string and replace them

I have one word. At first, instead of letters, there are only ?. So for example, word 'town' would be shown like this '????'.
Then user guesses letter and if he nails it, it changes from ? to actual letter.
For example, if he guesses t, it would look like this 't???'.
The problem is, that I have no idea, how to go through string and divide it to characters. And if I somehow do it, I cannot change it in the new string.
Should look somehow like this.
word do: [ :c |
c = guessedChar
ifTrue[mask := mask, guessedChar]
ifFalse[mask := mask, '?']
].
mask is initialized to nil, because the word length can change and word is String.
guessedChar is connected to inputField, however it contains only one character at a time.
And would it be better, do it once for every guessedChar or hold collection of all guessed characters and run it over every time?
A String is a Collection of Character objects. So you can use the same methods that apply to other collections, too (like #select:, #collect: or #reject:)
guessedCharacters := 'ts'.
mask := word collect:[:each | (guessedCharacters includes: each)
ifTrue:[each]
ifFalse:[$?]].
Please note that 't' is a String with the Character t. A Character can be written with $ prefix as literal character $t.
As String is a subclass of SequenceableCollection you can concatenate two Strings via ,. You cannot however concatenate a String and a Character.
Instead you could use #copyWith: to append a Character to a String. The result is a new String, it wouldn't modify the existing instance.
You could use
word doWithIndex: [:c :i | c = guess ifTrue: [mask at: i put: c]]
which is equivalent to:
i := 1.
word do: [:c |
c = guess ifTrue: [mask at: i put: c].
i := i + 1]
except that you don't have to initialize and increment i (which is a little bit more error prone, and more verbose)
Addendum
Given that instances of String cannot grow or change their size, which is immutable, I assume that what might change is the variable word. In that case you should initialize mask accordingly, so both strings keep always the same length. Like this:
word: aString
word := aString.
mask := word copy atAllPut: $?
If you also want to preserve the characters already guessed:
word: aString
| guessed |
word := aString.
guessed := mask reject: [:c | c = $?].
mask := word copy atAllPut: $?.
guessed do: [:c | self try: c].
Where #try: is the method we had before
try: aCharacter
word doWithIndex: [:c :i | c = aCharacter ifTrue: [mask at: i put: c]]
(you may want to uppercase things if required)
"initially"
actualWord := 'phlebotomy'.
actualMask := actualWord collect: [:ch| $?].
"after each guess"
word := actualWord readStream.
mask := actualMask readStream.
newMask := WriteStream on: String new.
[ word atEnd
] whileFalse:
[ nextCh := word next = guessedCharcter
ifTrue: [mask skip. guessedCharacter]
ifFalse: [mask next].
newMask nextPut: nextCh
].
actualMask := newMask contents

Perl6-ish expression for the bits of an integer

I've been trying to exercise my Perl 6 chops by looking at some golfing problems. One of them involved extracting the bits of an integer. I haven't been able to come up with a succinct way to write such an expression.
My "best" tries so far follow, using 2000 as the number. I don't care whether the most or least significant bit comes first.
A numeric expression:
map { $_ % 2 }, (2000, * div 2 ... * == 0)
A recursive anonymous subroutine:
{ $_ ?? ($_ % 2, |&?BLOCK($_ div 2)) !! () }(2000)
Converting to a string:
2000.fmt('%b') ~~ m:g/./
Of these, the first feels cleanest to me, but it would be really nice to be able to generate the bits in a single step, rather than mapping over an intermediate list.
Is there a cleaner, shorter, and/or more idiomatic way to get the bits, using a single expression? (That is, without writing a named function.)
The easiest way would be:
2000.base(2).comb
The .base method returns a string representation, and .comb splits it into characters - similar to your third method.
An imperative solution, least to most significant bit:
my $i = 2000; say (loop (; $i; $i +>= 1) { $i +& 1 })
The same thing rewritten using hyperoperators on a sequence:
say (2000, * +> 1 ...^ !*) >>+&>> 1
An alternative that is more useful when you need to change the base to anything above 36, is to use polymod with an infinite list of that base.
Most of the time you will have to reverse the order though.
say 2000.polymod(2 xx *);
# (0 0 0 0 1 0 1 1 1 1 1)
say 2000.polymod(2 xx *).reverse;
say [R,] 2000.polymod(2 xx*);
# (1 1 1 1 1 0 1 0 0 0 0)

Not sure what this pseudo-code is saying

I saw this pseudo-code on another stackoverflow question found here Split a string to a string of valid words using Dynamic Programming.
The problem is a dynamic programming question to see if an input string can be split into words from a dictionary.
The third line, means to set an array b of size [N+1] to all false values? I'm pretty sure about that. But what I am really not sure about is the fifth line. Is that a for-loop or what? I feel like pseudo-code saying 'for i in range' would only have 2 values. What is that line saying?
def try_to_split(doc):
N = len(doc)
b = [False] * (N + 1)
b[N] = True
for i in range(N - 1, -1, -1):
for word starting at position i:
if b[i + len(word)]:
b[i] = True
break
return b
It's confusing syntax, and I'm pretty sure there's a mistake. It should be:
for i in range(N - 1, 0, -1) //0, not -1
which I believe means
for i from (N - 1) downto 0 //-1 was the step, like i-- or i -= 1
This makes sense with the algorithm, as it simply starts at the end of the string, and solves each trailing substring until it gets to the beginning. If b[0] is true at the end, then the input string can be split into words from the dictionary. for word starting at position i just checks all words in the dictionary to see if they start at that position.
If one wants to be able to reconstruct a solution, they can change b to an int array, initialize to 0s, and change the if to this:
if b[i + len(word)] != 0
b[i] = i + len(word) //len(word) works too
break

Maple Sequence Length

I'm trying to create a basic program in Maple that runs the Collatz sequence when given a number (n) from the user. For those that don't know, the Collatz sequence is basically "If the number given is odd, do 3n + 1, if it is even, divide by 2 and continue to do so for every answer. Eventually, the answer will reach 1"
I'm trying to grab the number of iterations that the sequence is performed, say if the sequence is run through 10 times, it prints that out. Here is my current code:
Collatz := proc (n::posint)
if type(n, even) then (1/2)*n
else 3*n+1
end if
end proc
CollSeq := proc (n::posint)
local i;
i := n;
while 1 < i do
lprint(i);
i := Collatz(i)
end do
end proc
This so far works, and if the proc CollSeq(50) is entered, it will perform the Collatz sequence on 50 until it reaches 1. The bit I am stuck on is the length of the sequence. I have read around and learned that I might be able to use the nops([]) function of Maple to get the length of the sequence. Here is what I have tried:
CollLen := proc (n::posint)
local c;
c := CollSeq(n);
print(nops([c]))
end proc
I have a feeling this is horribly wrong. Any help would be much appreciated.
Many Thanks
Your function fails to return the actual sequence of values. You need to accumulate it as you go through the loop.
CollSeq := proc (n::posint)
local i, s;
i := n;
s := i;
while 1 < i do
lprint(i);
i := Collatz(i);
s := s, i;
end do;
s;
end proc
The lprint() command just prints its argument to the terminal (showing it on screen), it DOES not save it in a list. And nops() or a better command numelems() counts the number of elements in a list! So putting nops around something that has lprint will not count the number of things. Instead of using lprint in your second function (procedure), define a list, or better than list, an array and in the lprint-line, use a command to append the new number to your growing collection. If you want to see these numbers, just print this collection. Now this time, your third function can have a meaning and it will work as you expected.
Here is the closest fix to your codes.
Collatz := proc( n :: posint )
if type(n, even) then
return( n/2 ):
else
return( 3*n+1 ):
end if:
end proc:
CollSeq := proc ( n :: posint )
local
i :: posint,
c :: 'Array'( posint ):
i := n:
c := Array([]):
while 1 < i do
ArrayTools:-Append( c, i ):
i := Collatz( i ):
end do:
return( c ):
end proc:
CollLen := proc ( n :: posint )
local c :: posint:
c := CollSeq( n ):
return( numelems( c ) ):
end proc:
Here is a screenshot of using them in a Maple worksheet.
Why do I use an array and not a list? Because if you use a list which is immutable, each time you want to add an element to it, in fact it is defining a new list. It is not a memory efficient way, while array is mutable and your edits modifies the array itself. See the help pages on these in Maple.
And looking at your codes, it seems you have the same problem that some of my students in their first programming course usually have, return and print are not the same thing. If you really want a "print" action, that is fine, but you should not expect that the printed value be the output of the function unless you are using a return line inside the function that returns the same value of the print as well. For example you can have print(c): before return(c): in the second function above. So it both prints the sequence on the terminal and returns it to be used by another function or line of code.

Smalltalk syntax error

I'm learning smalltalk right now and am trying to make a very simple program that creates an array of numbers and then finds the largest number. My code looks like this:
| list max |
list := #(1 8 4 5 3).
1 to: list size do: [:i |
max < (list at: i)
ifTrue: [max := (list at: i)].
ifFalse: [max := max].
].
When I run this code, I get "stdin:7: parse error, expected ']'". I'm a bit confused as to what is causing this. It looks to me like all of my square brackets correspond. Help?
Alexandre told you already that is likely that collection provides a max method. You might be interested in a few ways how to do it.
Using collection max (maximum of all elements)
#(1 8 4 5 3) max
Using number max: (which of two numbers is bigger)
#(1 8 4 5 3) inject: 0 into: [:max :elem|
max max: elem ]
Or using just using the internal iterator
#(1 8 4 5 3) inject: 0 into: [:max :elem|
max < elem
ifTrue: [ elem ]
ifFalse:[ max ] ]
Together with your solution to use an external iteration you can see there are a lot of possibilities.
Hope it adds something
The line number seems off but looking at your code it seems the error is probably caused by the period after ifTrue: [max := (list at: i)]. #ifTrue:ifFalse is a single method selector and it doesn't make sense to break it into two statements.
Actually you could remove the ifFalse part of the code entirely. Assigning max to itself has no effect. Also looping on the indices is not needed here. You can work on the values directly with list do: […].
The max variable should also be initialized. Zero seems like a good choice to compare against the positive numbers in your array.
But instead of doing all that look into the Collection class. Your Smalltalk dialect may already offer a max method for this task.
Your immediate problem is that you terminate the ifTrue section with a period. You could normally just remove the period but, since your ifFalse section is effectively a non-operation, it's probably better to remove that bit.
But even when you fix that, you still need to initialise max so that you don't get a < message being sent to the nil object. You can initialise it to the first element, if there are any, then you can change the loop to start at the second.
Of course, initialising to the first element is problematic when the list is empty so you should handle that as well. In the code below, I've set it to a suitable small value then initialised it from the first element of the list only if it's available.
Corrected code is:
| list max |
list := #(1 8 4 5 3).
max := -99999.
(list size) > 0 ifTrue: [max := (list at: 1)].
2 to: list size do: [:i |
max < (list at: i) ifTrue: [max := (list at: i)].
]
(max) displayNl
This outputs 8 as expected and also works fine on the edge cases (list size of zero and one).