What's the idiomatic way to detect sequences of x times the same object (or an object with a specific matching parameter) in an OrderedCollection or Array?
E.g. does the Array contain 10 times the number 5 in a row?
What's the idiomatic way to detect sequences of x times the same object (or an object with a specific matching parameter) in an OrderedCollection or Array?
E.g. does the Array contain 10 times the number 5 in a row?
Getting the sequences of repeating objects is as simple as:
({ 1. 1. 2. 2. 2. 5. 5. 3. 9. 9. 9. 9. } as: RunArray) runs
=> #(2 3 2 1 4)
If you want to test if there is a run satisfying specific constraints, you can do something like the following:
meetsConstraint := false.
({ 1. 1. 2. 2. 2. 5. 5. 3. 9. 9. 9. 9. } as: RunArray) runsAndValuesDo: [:run :value |
meetsConstraint := (value = 9 and: [run > 3])].
If you want to test for a certain property of an object instead of object equality, you can easily create a RunArray
of this property by doing a collect:
on it.
So the generalized solution would look something like this:
SequenceableCollection >> containsRunOf: anElement withAtLeast: nElements
(self as: RunArray) runsAndValuesDo: [:run :value |
(value = anElement and: [run >= nElements]) ifTrue: [^ true]].
^ false
And then:
({ 'aa'. 'bb'. 'c'. 'ddd'. } collect: [:each | each size])
containsRunOf: 2 withAtLeast: 3
=> false
I like Uko's answer and would like to provide a different solution which addresses the "matching parameter" part of your question. In SequenceableCollection
define:
contains: m consecutiveElementsSatisfying: block
| n i |
self isEmpty ifTrue: [^m = 0].
n := self size - m + 1.
i := 1.
[i <= n] whileTrue: [| j |
(block value: (self at: i)) ifTrue: [
j := 2.
[j <= m and: [block value: (self at: i + j - 1)]]
whileTrue: [j := j + 1].
j > m ifTrue: [^true]].
i := i + 1].
^false
Now, for example the two following expressions would evaluate to true
#(2 1 1 1 2) contains: 3 consecutiveElementsSatisfying: [:e | e = 1]
#(2 1 0 1 2) contains: 3 consecutiveElementsSatisfying: [:e | e squared = e]
Note: If you want the startingAt: n
version of this method just initialize i := n
instead of i := 1
just before the main loop.
EDIT:
And, of course, we can complete the protocol with the following method in SequenceableCollection
:
contains: m consecutiveTimes: anObject
^self contains: m consecutiveElementsSatisfying: [:e | e = anObject]
And the example:
#(2 1 1 1 2) contains: 3 consecutiveTimes: 1
I'd say that you have to follow a pattern like this:
(collectionToTest
indexOfSubCollection: (
Array
new: numberOfRepetitions
withAll: desiredObject)
startingAt: 1
) isZero not
Maybe I don't know some of useful methods in Pharo, but if you define the ones like:
SequenceableCollection >> indexOfSubCollection: aSubCollection
^ aSubCollection indexOfSubCollection: aSubCollection startingAt: 0
SequenceableCollection >> containsSubCollection: aSubCollection
^ (aSubCollection indexOfSubCollection: aSubCollection) isZero not
Object >> asArrayOf: aLength
^ Array new: aLength withAll: self
then the definition can be flattened into:
collectionToTest containsSubCollection:
(desiredObject asArrayOf: numberOfRepetitions)
or for your example:
anArray containsSubCollection: (5 asArrayOf: 10)
P.S. I'm not sure about the method names. Maybe inArrayOf:
can be better then asArrayOf:
and so on.