NumberSet.st
author Claus Gittinger <cg@exept.de>
Thu, 09 May 1996 18:46:34 +0200
changeset 293 2086fa8d3ef3
parent 292 45529597a916
child 483 02a6c19890ed
permissions -rw-r--r--
checkin from browser

SequenceableCollection subclass:#NumberSet
	instanceVariableNames:'intervals'
	classVariableNames:''
	poolDictionaries:''
	category:'Collections-Ordered'
!

!NumberSet class methodsFor:'documentation'!

documentation
"
    NumberSets are Sets holding positive integers.
    This class has been written to represent number-ranges 
    from .newsrc-files.
    The implementation uses an array of intervals or numbers.

    Reading and writing is done in .newsrc-format.

    written spring 92 by claus

    [author:]
        Claus Gittinger

    [see also:]
        NewsHandler
"
! !

!NumberSet class methodsFor:'instance creation'!

with:aNumberOrInterval
    "return a new numberSet containing a single number
     or the numbers from an interval"

    ^ self new setFirst:aNumberOrInterval

    "
     NumberSet with:69
     NumberSet with:(1 to:50)
    "

    "Modified: 9.5.1996 / 17:25:18 / cg"
! !

!NumberSet class methodsFor:'input from a stream'!

readFrom:aStringOrStream
    "read a NumberSet (in .newsrc format) from aStream.
     return an empty NumberSet if nothing can be read (or wrong format)"

    ^ self new readFrom:aStringOrStream

    "
     NumberSet readFrom:(ReadStream on:'0-62,69,82,84,86,88,91')
    "

    "Modified: 9.5.1996 / 18:45:37 / cg"
! !

!NumberSet methodsFor:'accessing'!

lastOfFirstInterval
    "return the last element of the first interval.
     Return nil if the receiver is empty."

    |element|

    intervals isNil ifTrue:[^ nil].

    element := intervals at:1.
    (element isNumber) ifFalse:[^ element stop].
    ^ element

    "Modified: 9.5.1996 / 17:31:37 / cg"
! !

!NumberSet methodsFor:'adding & removing'!

add:aNumber
    "add the argument, aNumber to the NumberSet"

    |index endIndex searching element nextElement newIntervals|

    intervals isNil ifTrue:[
        intervals := OrderedCollection with:aNumber.
        ^ self
    ].

    "/ search for an interval to expand (on the fly watch out if already there)

    index := 1.
    endIndex := intervals size.
    searching := true.
    [searching] whileTrue:[
        element := intervals at:index.
        (element isNumber) ifTrue:[
            "make a number to an interval"
            (element = (aNumber - 1)) ifTrue:[ 
                intervals at:index put:(element to:aNumber).
                self tryMerge:index.
                ^ self
            ].
            (element = (aNumber + 1)) ifTrue:[ 
                intervals at:index put:(aNumber to:element).
                self tryMerge:(index - 1).
                ^ self
            ]. 
            "already in set"
            (element = aNumber) ifTrue:[^ self].
            "not in - stop search"
            (element > aNumber) ifTrue:[searching := false]
        ] ifFalse:[
            "expand interval"
            (element start = (aNumber + 1)) ifTrue:[ 
                element start:aNumber.
                self tryMerge:(index - 1).
                ^ self
            ]. 
            (element stop = (aNumber - 1)) ifTrue:[ 
                element stop:aNumber.
                self tryMerge:index.
                ^ self
            ].
            "already in set"
            ((element start <= aNumber) and:[
             (element stop >= aNumber)]) ifTrue:[^ self].
            "not in - stop search"
            (element start > aNumber) ifTrue:[searching := false]
        ].
        searching ifTrue:[
            index := index + 1.
            (index > endIndex) ifTrue:[searching := false]
        ]
    ].

    "/ append at end
    (index > endIndex) ifTrue:[
        intervals add:aNumber.
        ^ self
    ].

    "/ insert before index

    intervals add:aNumber beforeIndex:index.

    "Modified: 9.5.1996 / 17:31:05 / cg"
!

remove:aNumber ifAbsent:aBlock
    "remove the argument, aNumber from the NumberSet"

    |index endIndex element newIntervals|

    (intervals isNil or:[intervals size == 0]) ifTrue:[
        ^ aBlock value
    ].

    index := 1.
    endIndex := intervals size.
    [true] whileTrue:[
        element := intervals at:index.
        (element isNumber) ifTrue:[
            (element = aNumber) ifTrue:[
                (intervals size == 1) ifTrue:[
                    intervals := nil
                ] ifFalse:[
                    intervals removeIndex:index
                ].
                ^ self
            ].
            "not in - stop search"
            (element > aNumber) ifTrue:[^ aBlock value]
        ] ifFalse:[
            "can remove from interval ?"
            (element start = aNumber) ifTrue:[ 
                element start:(aNumber + 1).
                (element start = element stop) ifTrue:[
                    intervals at:index put:(element start)
                ].
                ^ self
            ]. 
            (element stop = aNumber) ifTrue:[ 
                element stop:(aNumber - 1).
                (element start = element stop) ifTrue:[
                    intervals at:index put:(element start)
                ].
                ^ self
            ].
            (element start > aNumber) ifTrue:[^ aBlock value].
            ((element start <= aNumber) and:[
             (element stop >= aNumber)]) ifTrue:[
                "must make it two intervals"
                newIntervals := OrderedCollection new:(endIndex + 1).
                newIntervals replaceFrom:1 to:(index - 1) with:intervals.
                newIntervals replaceFrom:(index + 2) to:(endIndex + 1)
                                    with:intervals startingAt:(index + 1).
                (element start = (aNumber - 1)) ifTrue:[
                    newIntervals at:index put:element start
                ] ifFalse:[
                    newIntervals at:index put:(element start to:(aNumber - 1))
                ].
                ((aNumber + 1) = element stop) ifTrue:[
                    newIntervals at:(index + 1) put:(aNumber + 1)
                ] ifFalse:[
                    newIntervals at:(index + 1) put:((aNumber + 1) to:element stop)
                ].
                intervals := newIntervals.
                ^ self
            ]
        ].
        index := index + 1.
        (index > endIndex) ifTrue:[^ aBlock value]
    ]

    "Modified: 9.5.1996 / 17:30:52 / cg"
! !

!NumberSet methodsFor:'enumerating'!

do:aBlock
    "evaluate the argument, aBlock for each element in the numberSet"

    intervals isNil ifTrue:[^ self].
    intervals do:[:element |
        (element isNumber) ifTrue:[
            aBlock value:element
        ] ifFalse:[
            element do:aBlock
        ] 
    ]

    "Modified: 9.5.1996 / 17:30:19 / cg"
! !

!NumberSet methodsFor:'printing'!

displayString
    "return a nice string"

    |first s|

    intervals size == 0 ifTrue:[^ 'NumberSet()'].

    s := 'NumberSet('.
    first := true.
    intervals do:[:element |
        first ifTrue:[
            first := false
        ] ifFalse:[
            s := s , ','
        ]. 
        (element isNumber) ifTrue:[
            s := s , (element printString)
        ] ifFalse:[
            s := s , (element start printString).
            s := s , '-'.
            s := s , (element stop printString)
        ] 
    ].
    s := s , ')'.
    ^ s

    "Modified: 9.5.1996 / 17:30:14 / cg"
!

printOn:aStream
    "output the NumberSet (in .newsrc format) on aStream"

    |first|

    intervals isNil ifTrue:[^ self].

    first := true.
    intervals do:[:element |
        first ifTrue:[
            first := false
        ] ifFalse:[
            aStream nextPut:$,
        ]. 
        (element isNumber) ifTrue:[
            aStream nextPutAll:(element printString)
        ] ifFalse:[
            aStream nextPutAll:(element start printString).
            aStream nextPut:$-.
            aStream nextPutAll:(element stop printString)
        ] 
    ]

    "Modified: 9.5.1996 / 17:30:10 / cg"
! !

!NumberSet methodsFor:'private'!

readFrom:aStringOrStream
    "read my value from aStream (in .newsrc format)"

    |s nextChar arr start stop done|

    s := aStringOrStream readStream.

    s skipSeparators.
    s atEnd ifTrue:[^ nil].

    intervals := OrderedCollection new.

    done := false.
    [done] whileFalse:[
        s atEnd ifTrue:[
            done := true
        ] ifFalse:[
            start := Number readFrom:s.
            start isNil ifTrue:[
                done := true
            ] ifFalse:[
                s skipSeparators.
                s atEnd ifTrue:[
                    intervals add:start. 
                    done := true
                ] ifFalse:[
                    nextChar := s peek.
                    (nextChar == $-) ifTrue:[
                        s next.
                        stop := Number readFrom:s.
                        intervals add:(start to:stop)
                    ] ifFalse:[
                        intervals add:start
                    ].
                    s skipSeparators.
                    s atEnd ifTrue:[
                        done := true
                    ] ifFalse:[
                        nextChar := s peek.
                        (nextChar == $,) ifFalse:[
                             done := true
                        ] ifTrue:[
                             s next
                        ]
                    ]
                ]
            ]
        ]
    ].
    (intervals size == 0) ifTrue:[
        intervals := nil
    ]

    "
     NumberSet new readFrom:(ReadStream on:'0-62,69,82,84,86,88,91')
     NumberSet new readFrom:'0-62,69,82,84,86,88,91'
    "

    "Modified: 9.5.1996 / 18:46:26 / cg"
!

tryMerge:index
    "try to make element at index and index + 1 be one element"

    |element first last first2 last2 nextElement|

    (index > 0) ifTrue:[
        (index < (intervals size)) ifTrue:[
            element := intervals at:index.
            (element isNumber) ifFalse:[
                first := element start.
                last := element stop
            ] ifTrue:[
                first := element.
                last := element
            ].
    
            nextElement := intervals at:(index + 1).
            (nextElement isNumber) ifFalse:[
                first2 := nextElement start.
                last2 := nextElement stop
            ] ifTrue:[
                first2 := nextElement.
                last2 := nextElement
            ].
    
            (last >= (first2 - 1)) ifTrue:[
                intervals at:index put:(first to:last2).
                intervals removeIndex:(index + 1)
            ]
        ]
    ]

    "Modified: 9.5.1996 / 17:29:50 / cg"
! !

!NumberSet methodsFor:'private accessing'!

setFirst:aNumberOrInterval 
    "set the first element"

    intervals := OrderedCollection with:aNumberOrInterval

    "Modified: 9.5.1996 / 17:26:26 / cg"
! !

!NumberSet methodsFor:'queries'!

first
    "answer the first i.e. lowest number in the set.
     If the receiver is empty, return nil."

    |element|

    intervals isNil ifTrue:[^ nil].

    element := intervals at:1.
    (element isNumber) ifTrue:[
        ^ element
    ].
    ^ element start

    "Modified: 9.5.1996 / 17:29:14 / cg"
!

includes:aNumber
    "answer true, if aNumber is in the set"

    |endIndex index middle delta element start stop|

    intervals isNil ifTrue:[^ false].

    index := 1.
    endIndex := intervals size.

    "/ use a binary search, to limit search

    middle := (endIndex + index) // 2.
    [(middle > (index + 1)) and:[middle < (endIndex - 1)]] whileTrue:[
        element := intervals at:middle.
        (element isNumber) ifFalse:[
            (element stop <= aNumber) ifTrue:[
                index := middle
            ] ifFalse:[
                (element start >= aNumber) ifTrue:[
                    endIndex := middle
                ] ifFalse:[
                    (element includes:aNumber) ifTrue:[^ true]
                ]
            ]
        ] ifTrue:[
            (element <= aNumber) ifTrue:[
                (element == aNumber) ifTrue:[^ true].
                index := middle
            ] ifFalse:[
                endIndex := middle
            ]
        ].
        middle := (endIndex + index) // 2
    ].

    [index <= endIndex] whileTrue:[
        element := intervals at:index.
        (element isNumber) ifFalse:[
            (element start > aNumber) ifTrue:[ ^ false].
            (element stop >= aNumber) ifTrue:[ ^ true]
        ] ifTrue:[
            (element > aNumber) ifTrue:[ ^ false].
            (element = aNumber) ifTrue:[ ^ true]
        ].
        index := index + 1
    ]. 

    ^ false

    "Modified: 9.5.1996 / 17:29:05 / cg"
!

last
    "answer the last i.e. highest number in the set.
     If the receiver is empty, return nil."

    |element|

    intervals isNil ifTrue:[^ nil].

    element := intervals last.
    (element isNumber) ifTrue:[
        ^ element
    ].
    ^ element stop

    "Modified: 9.5.1996 / 17:29:10 / cg"
!

size
    "return the number of elements in the Set"

    |tally|

    intervals isNil ifTrue:[^ 0].
    tally := 0.

    intervals do:[:element |
        (element isNumber) ifTrue:[
            tally := tally + 1
        ] ifFalse:[
            tally := tally + element size
        ] 
    ].
    ^ tally

    "Modified: 9.5.1996 / 17:29:20 / cg"
! !

!NumberSet class methodsFor:'documentation'!

version
    ^ '$Header: /cvs/stx/stx/libbasic2/NumberSet.st,v 1.6 1996-05-09 16:46:34 cg Exp $'
! !