Idiomatic way to detect sequences of x times same

2019-06-26 08:54发布

问题:

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?

回答1:

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


回答2:

I like Uko's answer and would like to provide a different solution which addresses the "matching parameter" part of your question. In SequenceableCollectiondefine:

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


回答3:

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.