How do we iterate and select an element of a set in pharo? - smalltalk

My collection is a Set that contains a number of dictionaries. How can iterate over each dictionary in the Set to select a specific key.
a Set(a Dictionary('age'->'25' 'code'->2512) a Dictionary('age'->'40' 'code'->'1243') a Dictionary('age'->'35' 'code'->'7854'))

set := {
{ 'age'->'25'. 'code'->'2512' } asDictionary .
{ 'age'->'40'. 'code'->'1243' } asDictionary.
{ 'age'->'35'. 'code'->'7854' } asDictionary.
} asSet.
If you are interested in retrieving just a single item, then detect: is the way to go. It will return the first item matching the predicate (the block). Note that Set has no defined order, so if you have multiple items matching, it may return different ones at different time.
d := set detect: [ :each | (each at: 'code') = '1243' ].
d. "a Dictionary('age'->'40' 'code'->'1243' )"
If you want to retrieve multiple items that all match the predicate, then use select:
multi := set select: [ :each | (each at: 'age') asNumber >= 35 ].
multi. "a Set(a Dictionary('age'->'40' 'code'->'1243' ) a Dictionary('age'->'35' 'code'->'7854' ))"
Update from comment for commenting:
As Carlos already stated, collect: will do what you need. It applies the transformation block to every item in the collection and then returns a collection of results.
codes := set collect: [ :each | each at: 'code' ].
Works for any collection
#(2 3 4) collect: [ :each | each squared ] "#(4 9 16)"
For further I recommend going through the Collections chapter in Pharo By Example book https://ci.inria.fr/pharo-contribution/job/UpdatedPharoByExample/lastSuccessfulBuild/artifact/book-result/Collections/Collections.html

mySet do: [:each | each do: [ :i | i doStuff ]]
or use detect (I`m not sure if detect works like this, I never used it so far):
mySet do: [:i | i detect: [ :each| (each at: 'key') doStuff ]].
or use keysDo:
mySet do: [:each | each keysDo: [ :k | k doStuff ]]
Check out: http://pharo.gforge.inria.fr/PBE1/PBE1ch10.html

Related

How can we sort dictionaries in an array by a specific key in pharo?

I have an array containing several dictionaries. How can I sorted them using a key that each dictionary have like age?
an Array((a Dictionary('age'->'20' 'ID'->1254))(a Dictionary('age'->'35' 'ID'->1350))(a Dictionary('age'->'42' 'ID'->1425)))
You can sort by providing a comparator block; the block takes two arguments (two elements from the array) and is expected to return boolean.
data := {
{ 'age' -> '20'. 'ID' -> 1254 } asDictionary.
{ 'age' -> '35'. 'ID' -> 1350 } asDictionary.
{ 'age' -> '42'. 'ID' -> 1425 } asDictionary
}.
sorted := data sorted: [ :a :b | (a at: 'age') > (b at: 'age') ].
sorted: will return a sorted collection without changing the receiver
sort: will perform the sorting in-place and return itself
You can also use asSortedCollection: which will create a new collection that always upholds the sorting invariant.
sc := data asSortedCollection: [ :a :b | (a at: 'age') > (b at: 'age') ].
"automatically inserted between age 42 and 35"
sc add: {'age' -> '39'. 'ID' -> 1500} asDictionary.
sc "a SortedCollection(a Dictionary('ID'->1425 'age'->'42' ) a Dictionary('ID'->1500 'age'->'39' ) a Dictionary('ID'->1350 'age'->'35' ) a Dictionary('ID'->1254 'age'->'20' ))"

Visibilily in #inject:into: block

This code:
((1 to: 10)
inject: (WriteStream on: String new)
into: [ :strm :each |
((each rem: 3) = 0)
ifTrue: [
strm
nextPutAll: each printString;
space;
yourself ]]) contents
fails because strm is undefined where it is used in the ifTrue: block. Why is it not visible there?
Edit: I tried it out in VASt and Pharo.
The problem is that the implied ifFalse: branch returns nil. To fix this, try the following:
((1 to: 10)
inject: (WriteStream on: String new)
into: [ :strm :each |
((each rem: 3) = 0)
ifFalse: [strm] "This is needed to avoid nil being returned"
ifTrue: [
strm
nextPutAll: each printString;
space;
yourself ]]) contents
Depending on the dialect (methods available), you can take much shorter approach
((1 to: 10) select: [ :each | (each rem: 3) = 0 ]) joinUsing: ' '
As a rule of thumb¹, any collection do: [ :each | something ifTrue: [] ] can be turned into much more straight-forward and readable collection select: [] or collection reject: []
Doing so will spread out the complexity over several independent steps (1. filtering, 2. adding to stream), instead of shoving it all together.
Or if you want to stick to your original
(((1 to: 10) select: [ :each | (each rem: 3) = 0 ])
inject: (WriteStream on: String new)
into: [ :stream :each |
stream
nextPutAll: each printString;
space;
yourself ]) contents
or
String streamContents: [ :stream |
(1 to: 10)
select: [ :each | (each rem: 3) = 0 ]
thenDo: [ :each |
stream
nextPutAll: each printString;
space
]
]
¹So not always, but always good to keep in mind when you encounter such situation.

How to find all methods available in Smalltalk and search by name?

In Smalltalk, is there a way to search for all methods available (of any object), say, that contain the word convert (case insensitive search), and also contain the word string? (the method name, not the source code)
In Smalltalk you have direct access to all classes, their methods and their source code, so you can go through them.
Pharo
Go over all the classes and then from each class select all methods that match your needs (or use the Finder tool).
Object withAllSubclasses flatCollect: [ :cls |
cls methods select: [ :method |
(method selector includesSubstring: 'convert' caseSensitive: false) and: [
(method selector includesSubstring: 'string' caseSensitive: false) ]
]
].
GNU Smalltalk
GST doesn't have as nice API, but it can be done also.
(Object withAllSubclasses collect: [ :cls |
cls methodDictionary ifNotNil: [ :dict |
dict values select: [ :method |
(method selector asLowercase indexOfSubCollection: 'convert' asLowercase) > 0 and: [
(method selector asLowercase indexOfSubCollection: 'string' asLowercase) > 0 ]
]
]
]) join
VisualWorks
(also Pharo and Squeak, and with ifNotNil: also GNU Smalltalk)
VW doesn't have #flatten, so it's implemented explicitly. For case-insensitive search #findSameAs:startingAt:wildcard: can also be used.
(Object withAllSubclasses collect: [ :cls |
cls methodDictionary values select: [ :method |
(method selector asLowercase findString: 'convert' asLowercase startingAt: 1) > 0 and: [
(method selector asLowercase findString: 'string' asLowercase startingAt: 1) > 0 ]
]
]) inject: #() into: [ :arr :each | arr, each ]
Dolphin
Dolphin seems to have different object model, see Leandro's answer below.
This may not work on all smalltalk dialects, but it works at least
with squeak and pharo (other smalltalks may have similar tools/classes)
SystemNavigation default browseAllSelect:[:e |
(e selector includesSubstring:'convert' caseSensitive:false)
and:[e selector includesSubstring:'string' caseSensitive:false]]
This is more a complement to the answer given by #Peter.
Be aware that in some dialects (e.g., Dolphin) the message #withAllSubclasses will only collect classes, and not metaclasses. Because of that the enumerations in #Peter's answer should add all metaclasses in an explicit way.
For instance,
selectors := OrderedCollection new.
Object withAllSubclasses do: [:class | | matching |
matching := class selectors select: [:s |
(s includesString: 'onvert') and: [s includesString: 'tring']].
selectors addAll: matching.
matching := class class selectors select: [:s |
(s includesString: 'onvert') and: [s includesString: 'tring']].
selectors addAll: matching].
^selectors
Note BTW that I've removed the first letter from both 'convert' and 'string' to cheaply prevent case mismatches.
Another difference with my code is that it iterates over the selectors of a class rather than over its methods.
UPDATE
Note that I've used two enumerations because we cannot do this:
class selectors , class class selectors select: [:s |
etc. The reason is that the selectors of a class come in a Set and these do not understand #,.
We could have done instead:
all := class selectors addAll: class class selectors; yourself.
all selectors select: [:s |
etc. (note the use of #yourself)
This is to add different flavor to the list above.
#Smalltalk/X
Object withAllSubclasses flatCollect: [ :cls |
cls methodDictionary values select: [ :method |
(method selector includesSubstring: 'convert' caseSensitive: false) and: [
(method selector includesSubstring: 'string' caseSensitive: false) ]
]
].

Perform block for each satisfied condition, otherwise perform other block

Imagine that you have to select some values and for each of them you have to evaluate a block. On the other hand if there is no value that satisfies the condition another block has to be evaluated.
Example:
Consider the next method signature:
forPositivesOf: aCollection do: aBlock otherwise: defaultBlock
This method should evaluate a block aBlock with every positive element of aCollection, but if there are no elements like that, evaluate defaultBlock. Please note that in reality the method may calculate something more complex than just positive numbers, and instead of aCollection there can be a much complex object.
A more compact version of the first alternative is the following that doesn't instantiate a new closure, and just uses the ones received as arguments.
forPositivesOf: aCollection do: aBlock otherwise: defaultBlock
^(aCollection select: [:each | each positive ])
ifEmpty: defaultBlock
ifNotEmpty: [ :collection | collection do: aBlock ]
Taking Uko's solution a bit further:
forPositivesOf: aCollection do: aBlock otherwise: defaultBlock
^ (aCollection
select: #positive
thenCollect: aBlock
) ifEmpty: defaultBlock
At the moment I see two solutions:
1)
forPositivesOf: aCollection do: aBlock otherwise: defaultBlock
(aCollection select: #positive)
ifEmpty: [ defaultBlock value ]
ifNotEmpty: [ :collection |
collection do: [ :el | aBlock cull: el ] ]
but in case calculation of positive is expensive it would be good to evaluate aBlock for the first encountered element, as then the one who passed aBlock will be able to react in any desired way.
2)
forPositivesOf: aCollection do: aBlock otherwise: defaultBlock
| encountered |
encountered := false.
aCollection do: [ :el |
el positive ifTrue: [
encountered := true.
aBlock cull: el ] ].
encountered ifFalse: [
defaultBlock value ]
But I don't like the extra encountered variable, it makes code less functional.
One really nice functional way that works for SequenceableCollections:
forPositivesOf: aCollection do: aBlock otherwise: defaultBlock
(aCollection
select: #positive
thenCollect: [ :el | aBlock cull: el ]) ifEmpty: [ defaultBlock value ]

How to get all items in from of Text from Ordered collection

simple question i got
|list string|
list:= #('ab' 'efghij' 'lmnopqrst'). "Ordered collection"
list do:[:each| "string with:each" i know this is not right how do i add items ].
I tried streams too it returned me this "an ordered collection('ab' 'efghij' 'lmnopqrst')"
All i need is a single Text that has
'abc efghij lmnopqrst '
In Pharo you can do
Character space join: list
If join: is not available and it should perform well then you can use a stream variant
String streamContents: [:stream|
list
do [:each| stream nextPutAll: each ]
separatedBy: [ stream nextPut: Character space ]
Object class has defined a #asString message that reads:
"Answer a string that represents the receiver."
So, you can do:
| aList aStringsList |
aList := #('ab' 'efghij' 'lmnopqrst'). "Array"
aStringsList := aList collect: [ :each | each asString ]
And aStringsList will be an Array of the Strings returned by the invocation of #asString in each of aList's members.
If you want to concatenate all of them in a single String, you can use the #inject:into: method of the collections instead of #collect::
aList inject: '' into: [ :text :each | text , each asString , ' ' ]
If you print that you'll get the 'ab efghij lmnopqrst ' you want :)