[squeak-dev] Collections/Streams | About enumerating a sequence up to a matching query ...

Nicolas Cellier nicolas.cellier.aka.nice at gmail.com
Mon Mar 8 11:22:16 UTC 2021


First, concerning the inclusion of the matching element, Stream upTo:
has always excluded that element.
VW has introduced through: which is the inclusive variant of upTo:
I notice that we have adopted that semantic in Collection protocol
with copyUpThrough: versus copyUpTo: , but did not implement through:
in Stream...
My advice would be to stick with those semantics as much as possible
to avoid confusion*.

Concerning the usage of a predicate rather than an element, I don't
see an existing method indeed.
It could be something like: ^self first: (self findFirst: predicate),
but that would answer an empty collection when no element matches,
contrary to copyUpTo:... The handling of zero makes it mandatory to
use periphrases (and temps).
I see that we lack a findFirst:ifNone:...
Of course, if you want then to collect:, then it would cost an
intermediate collection creation and two scans. The alternative would
be to use lazy proxies like in Xtreams, selecting: instead of select,
collecting: instead of collect:... By the way, there is already the
vocable limiting: in Xtreams, that must be fed with a position, but
findFirst: is not yet implemented (Xtreams are not necessarily
positionable).
Maybe we should inquire what clojure and the like have to propose in
this domain for some refreshing ideas...
https://clojuredocs.org/clojure.core/for

*side note: there's already some level of confusion between dialects.
For example, upTo: is not inclusive, but leave the stream position
AFTER the matching element.
    'abcdeh' readStream upTo: $c; next -> $d
But that's not necessarily the case with upToAll:
    'abcdeh' readStream upToAll: 'cd'; next -> $e in Squeak
    'abcdeh' readStream upToAll: 'cd'; next -> $c in VW

Le lun. 8 mars 2021 à 10:43, Marcel Taeumel <marcel.taeumel at hpi.de> a écrit :
>
> Hi all! :-)
>
> Here is an interesting issue that I repeatedly come across when it comes to enumerating a (sequenceable/ordered) collection up to a certain element, while collecting (i.e. mapping) new elements from that collection. It seems rather tedious to express in code, meaning more than 2-3 lines and maybe involving even temporary variables.
>
> Our stream protocol has #upTo:, which works when you know about the specific element you are looking for. It does not work, however, when you just have an indirect query that should match.
>
> Here is an example. From a list of integer numbers, extract all even ones up to (including) 50, then square those numbers.
>
> (1 to: 100)
>    select: [:num | num <= 50 and: [num even]]
>    thenCollect: [:num | num * num].
>
> A stream-based approach to shorten the predicate using #upTo: could look like this:
>
> ((1 to: 100) readStream upTo: 50)
>    select: [:num | num even]
>    thenCollect: [:num | num * num].
>
> Now imagine that, unfortunately, we don't know about "50" but only a query, say "a number that is squared 2500". Since #upTo: cannot do that and I do not want to jump collection-stream-collection for this issue, the non-stream approach could look like this:
>
> (1 to: 100)
>    select: [:num | (num * num <= 2500) and: [num even]]
>    thenCollect: [:num | num * num].
>
> Yet, this is still not my actual issue. What if that collection is not ordered at all but only has a stable sequence? Then we would need to mark the specific element and then reject every element that comes after that. (Note that we replaced #<= with #= to indicate "sequence" instead of "order".)
>
> | found |
> found := false.
> (1 to: 100)
>    select: [:num |
>       found := found or: [num * num = 2500].
>       found not and: [num even]]
>    thenCollect: [:num | num * num].
>
> Uh oh! We just skipped the stop element itself. What if we want to include it? Maybe like this:
>
> | found |
> found := false.
> (1 to: 100)
>    select: [:num | | firstMatch |
>       firstMatch := found not and: [num * num = 2500].
>       found := found | firstMatch.
>       found not | firstMatch and: [num even]]
>    thenCollect: [:num | num * num].
>
> Now this looks like a rather complicated implementation for a simple query. Let me give you a last approach using the infamous #valueWithExit.
>
> | list stop select collect result |
> list := 1 to: 100.
> stop := [:num | num * num = 2500].
> select := [:num | num even].
> collect := [:num | num * num].
> result := OrderedCollection new.
> [:break |
>    list do: [:num |
>       (select value: num) ifTrue: [result add: (collect value: num)].
>       (stop value: num) ifTrue: [break value]].
> ] valueWithExit.
>
> Phew. Is there a simpler approach to this problem? I am looking for something like #collect:until: or #upToSatisfying:. I do want the stop element to be included here, not sure about the general case.
>
> aButton withAllOwners
>    collect: [:morph | morph color]
>    until: [:morph | morph isSystemWindow].
>
> (aButton withAllOwners readStream
>    upToSatisfying: [:morph | isSystemWindow])
>    collect: [:morph | morph color].
>
> Best,
> Marcel
>


More information about the Squeak-dev mailing list