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

Marcel Taeumel marcel.taeumel at hpi.de
Mon Mar 8 09:43:12 UTC 2021


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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20210308/b78d70f7/attachment.html>


More information about the Squeak-dev mailing list