Smalltalk anomaly - why are the variables always the same, but when computing booleans they are different? - smalltalk

I chose to try out Smalltalk for AOC 2022 puzzle 4. I'm predicating on each line and increment the counter if the constraints are met. I'm trying to understand why the '2-8,3-7' line doesn't met the requirements. Therefore, I started printing out the values to check what's happening. Apparently, when printing out the values by sending displayNl message to the objects, the values firstMax, firstMin etc. are always the same through the loop, containing the info from '2-4,6-8', i.e. the first line. But still, what's even more weird, that the counter gets incremented once, even though the first line doesn't meet the constraints. Then, I figured out that it actually computes the boolean overlapFirst and overlapSecond values correctly, when checking the '6-6,4-6' line, hence ifTrue increments the counter! WHY!?
EDIT: I solved it by putting this instead of first putting the substrings into a variable:
firstAssignment := (line substrings: ',') first.
secondAssignment := (line substrings: ',') last.
Does it mean that you cannot reassign OrderedCollection?
I'm running this with gnu-small talk, by running command:
gst main.st
Here's data.txt.
2-4,6-8
2-3,4-5
5-7,7-9
2-8,3-7
6-6,4-6
2-6,4-8
Here's main.st.
file := FileStream open: 'data.txt' mode: FileStream read.
count := 0.
file linesDo: [
:line |
assignments := line substrings: ','.
firstAssignment := assignments first.
secondAssignment := assignments last.
first := firstAssignment substrings: '-'.
second := secondAssignment substrings: '-'.
firstMin := first first.
firstMax := first last.
secondMin := second first.
secondMax := second last.
overlapFirst := (firstMin <= secondMin) & (firstMax >= secondMax).
overlapSecond := (secondMin <= firstMin) & (secondMax >= firstMax).
overlap := overlapSecond | overlapFirst.
line displayNl.
overlapFirst displayNl.
overlapSecond displayNl.
firstMin displayNl.
firstMax displayNl.
secondMin displayNl.
secondMax displayNl.
overlap ifTrue: [
'Incremented!' displayNl.
count := count + 1.
].
].
Transcript show: count asString.
file close.

This solved my issue... I also edited the post, I'll need to learn how to do things in stackoverflow.
I changed lines 5 and 6.
file := FileStream open: 'data.txt' mode: FileStream read.
count := 0.
file linesDo: [
:line |
firstAssignment := (line substrings: ',') first.
secondAssignment := (line substrings: ',') last.
first := firstAssignment substrings: '-'.
second := secondAssignment substrings: '-'.
firstMin := first first asInteger.
firstMax := first last asInteger.
secondMin := second first asInteger.
secondMax := second last asInteger.
overlapFirst := (firstMin <= secondMin) & (firstMax >= secondMax).
overlapSecond := (secondMin <= firstMin) & (secondMax >= firstMax).
overlap := overlapSecond | overlapFirst.
line displayNl.
overlap ifTrue: [
'Incremented!' displayNl.
count := count + 1.
].
].
Transcript show: count asString.
file close.

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

Read file line of unknown size as string in VHDL

I'm trying to make a Test Bench where a file of one single line, where posible characters are "1" and "0". I've to read them all, and use one by one as input in my DUT.
So, in my TB, I've defined a process like the following, in order to read the file an pass it values to my DUT.
stim_proc: process
file input_file: TEXT is in "DatosEntrada.dat";
variable rdline : LINE;
variable line_content : string ( 1 to 4);
variable readed_char : character;
variable j : integer := 0;
begin
while not endfile(input_file) loop
readline(input_file, rdline);
--read(rdline, line_content);
for j in 1 to rdline'length-1 loop
readed_char := line_content(j);
if (readed_char = '1') then
input <= '1';
else
input <= '0';
end if;
wait for clk_period;
end loop;
end loop;
end process;
I'm reading the first (and only) line of my file with the first readline execution. After this, this loop shouldn't execute again.
Then, data from file should be inside rdline. So I've to process it. In order to do it, I've tried to loop over rdline length, but this loop doesn't execute.
for j in 1 to rdline'length-1 loop
So I thought I need to read this line in order to loop over it, and tried to move its data to a string var. The problem is that vector var like string need to have a defined size, and I don't know the file line size.
I've tried reading each time 4 chars from rdline into a string, process it, then repeat. However, I couldn't make it work.
I've found quite lot examples about reading files which have defined line formats, like columns or expected integers.
But how can I read an unknown text of one line?
This readed_char := line_content(j); doesn't work when line_content isn't loaded. Otherwise your attempt to read values is basically sound.
The end of line is not contained in a read LINE buffer, there's no reason to not read the last character of rdline. An end of line is signaled by one or more format effectors other than horizontal tab, and just the line contents are present.
There's also this inference that you have some relationship to a clock edge and not just a clock period. The following example shows that. Note you can also supply an offset from an edge using a wait for time_value.
A loop constant is declared in the loop statement. The variable j you declared is not the same j the loop uses. The loop statement hides the j in the outer declarative region (the variable declaration in the process statement).
Your code treats any other character in the string buffer than '1' as a '0'. I didn't change that, do demonstrate it. You should be aware of the impact.
A LINE is an allocated string of some length dependent on the length of a line in your read file. Every time you call readline the string rdline points to is updated. It doesn't leak memory, the previous buffer rdline pointed to released. You can read the length by using the 'RIGHT attribute or as in this case simply consume all the characters.
There may be line length limits in a VHDL tool implementation. There are none defined in the standard other than the maximum length of a string (POSITIVE'RIGHT).
An MCVE:
library ieee;
use ieee.std_logic_1164.all;
use std.textio.all;
entity foo is
end entity;
architecture fum of foo is
signal input: std_logic ;
signal clk: std_logic := '0';
constant clk_period: time := 10 ns;
begin
stim_proc:
process
file input_file: TEXT is in "DatosEntrada.dat";
variable rdline: LINE;
-- variable line_content : string ( 1 to 4);
-- variable readed_char : character;
-- variable j: integer := 0;
begin
while not endfile(input_file) loop
readline(input_file, rdline);
--read(rdline, line_content);
-- for j in 1 to rdline'length - 1 loop -- EOL not in rdline
for j in rdline'range loop
-- readed_char := line_content(j);
-- if readed_char = '1' then
if rdline(j) = '1' then -- changed
input <= '1';
else
input <= '0';
end if;
-- wait for clk_period; -- sync to edge instead
wait until falling_edge(clk); -- input related to clk edge
end loop;
end loop;
wait; -- added prevents needless loops
end process;
CLOCK:
process
begin
wait for clk_period/2;
clk <= not clk;
if now > 32 * clk_period then
wait;
end if;
end process;
end architecture;
And for DatosEntrada.dat containing:
11011110001HELLO11230000
That produces:
Where you can see all non '1' characters are interpreted as '0'.

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.
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.

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.

Line intersection code in pascal

I'm trying to write line intersection code to detect if two lines intersect.
the form i have stuff in is there are O objects that can have Lo(l subscript O) lines, each line has 2 points and each point has a x and a y.
this is the record format.
TPoint = record
x,y:integer;
end;
TLine = record
Point : array[0..1] of TPoint;
Color : Tcolor;
end;
TFill = record
Point : TPoint;
Color : Tcolor;
end;
TDObject = record
Lines : array of TLine;
Fills : array of TFill;
Rotation : integer;
Position : Tpoint;
BoundTop,Boundleft,Boundbottom,Boundright:integer;
end;
I call Code to iterate through each line combination of the two objects I wish to test for collision.
Function DoCollide(obj1,obj2:Tdobject):boolean;
var i,j:integer;
coll:boolean;
begin
coll:=false;
for i:=0 to length(obj1.lines) do
begin
for j:=0 to length(obj2.lines) do
begin
coll:=DoesIntersect(obj2.lines[i],obj2.lines[j])or coll;
end;
end;
result:=coll;
end;
each line test is done like so
Function DoesIntersect(Line1,Line2:Tline):boolean;
var
m1,m2,c1,c2,intersect:real;
v1,v2:Boolean;
begin
//return true if lines cross
// if line if verticle do not workout gradient
if ((line1.point[1].x)-(line1.point[0].x))=0 then
v1:=true // remember line 1 is verticle
else
begin
m1 := ((line1.point[1].y)-(line1.point[0].y))/((line1.point[1].x)-(line1.point[0].x));
c1 := line1.point[0].y - (line1.point[0].x*m1);
end;
if ((line2.point[1].x)-(line2.point[0].x))=0 then
v2:=true // remember line 2 is verticle
else
begin
m2 := ((line2.point[1].y)-(line2.point[0].y))/((line2.point[1].x)-(line2.point[0].x));
c2 := line2.point[0].y - (line2.point[0].x*m2);
end;
if ((NOT(m1=m2)) and (NOT(v1 or v2))) then // non parrellel and non verticle
begin
//lines cross find where
intersect := (c2-c1)/(m1-m2); //line intersect solved for x
if ((round(intersect)>= Min(line1.point[0].x,line1.point[1].x))
and(round(intersect)<=max(line1.point[0].x,line1.point[1].x))
and(round(intersect)>=min(line2.point[0].x,line2.point[1].x))
and(round(intersect)<=max(line2.point[0].x,line2.point[1].x))) then
result := true
else
result := false
end
else if (v1 and v2) then // both lines are parralel
begin
// double verticle parallel exeption
if (((line1.Point[0].y>=min(line2.Point[0].y,line2.Point[1].y))
and(line1.Point[0].y<=max(line2.Point[0].y,line2.Point[1].y)))
or ((line1.Point[1].y>=min(line2.Point[0].y,line2.Point[1].y))
and(line1.Point[1].y<=max(line2.Point[0].y,line2.Point[1].y)))
or ((line2.Point[0].y>=min(line1.Point[0].y,line1.Point[1].y))
and(line2.Point[0].y<=max(line1.Point[0].y,line1.Point[1].y)))
or ((line2.Point[1].y>=min(line1.Point[0].y,line1.Point[1].y))
and(line2.Point[1].y<=max(line1.Point[0].y,line1.Point[1].y)))) then
result := true
else
result := false;
end
else if (v1 and not v2) then // line 1 is verticle and line 2 is not
begin
if ((((line1.Point[0].x*m2+c2)>=min(line1.Point[0].y,line1.Point[1].y))
and ((line1.Point[0].x*m2+c2)<=max(line1.Point[0].y,line1.Point[1].y)))) then
result := true
else
result := false
end
else if (v2 and not v1) then // line 2 is verticle and line 1 is not
begin
if (((line2.Point[0].x*m1+c1)>min(line2.Point[0].y,line2.Point[1].y))
and ((line2.Point[0].x*m1+c1)<max(line2.Point[0].y,line2.Point[1].y))) then
result := true
else
result := false
end
else if (m1=m2) then // parrellel non verticle lines
begin
if (((line1.Point[0].x>=min(line2.Point[0].x,line2.Point[1].x))
and(line1.Point[0].x<=max(line2.Point[0].x,line2.Point[1].x)))
or ((line1.Point[1].x>=min(line2.Point[0].x,line2.Point[1].x))
and(line1.Point[1].x<=max(line2.Point[0].x,line2.Point[1].x)))
or ((line2.Point[0].x>=min(line1.Point[0].x,line1.Point[1].x))
and(line2.Point[0].x<=max(line1.Point[0].x,line1.Point[1].x)))
or ((line2.Point[1].x>=min(line1.Point[0].x,line1.Point[1].x))
and(line2.Point[1].x<=max(line1.Point[0].x,line1.Point[1].x)))) then
result := true
else
result := false;
end;
end;
but according to my code all lines always intersect..... thus I have made a mistake... am I doing this in a silly way any ideas what I have done wrong?
There are better ways of detecting whether two sets of lines intersect, but don't worry about that for now.
I'm concerned that your program even ran long enough for you to detect that everything intersects; you iterate beyond the bounds of your arrays, so your program should have crashed. Always leave range checking enabled.
If you're going to be doing geometry, you should make sure to differentiate between lines and line segments. In two dimensions, non-parallel lines always intersect. Even parallel lines can intersect if they are coincident. It would also behoove you to get the spellings of parallel and vertical correct.
Your calculation of intersect is wrong. You need to divide the difference in slopes by the difference in y-intercepts:
if c1 = c2 then
intersect := c1
else
intersect := (m1 - m2) / (c2 - c1);
If both lines are vertical, then it's not enough to check whether they overlap in their y coordinates. You also need to check that their x coordinates are equal. Likewise, with parallel non-vertical lines, you need to check whether the y-intercepts are equal.
If you fix all those problems and still get wrong results, then it's time to dust off the debugger. Find a pair of line segments that your function returns true for and yet do not really intersect. Call the function on those values and step through your function with the debugger. To make debugging easier, you'll want to split those many-line-long conditional expressions into several intermediate variables so you can check each one separately. Determine which calculation is wrong, and then fix it. Make sure your test dataset contains elements that will exercise each possible conditional path in your function.