AI
Animation
CGI
Compression
Console
Database
Debug
Dialects
Dialogs
Editor
Email
Encryption
Extension
External Library
File
File Handling
Files
Financial
FTP
Game
Games
Graphics
GUI
HTML
HTTP
Internet
LDC
Markup
Math
Module
Network
Networking
None
Other - Net
Parse
Patch
Printing
Protocol
Rebol
Scheme
Scientific
SDK
Security
Shell
Sound
SQL
TCP
Testing
Text
Text Processing
UI
User Interface
Util
Utility
VID
Visualization
Web
Win API
X-File
XML
rebol [
    title:   "Engroup"
    file:    %engroup.r

    version: 0.2
    date:    12-4-2009

    author: "Christian Ensel"

    purpose: "Mezzanine function to arrange values into groups of all equal values."

    comment: {
        What's with the superfluous WORD! argument in the simple envocation?
        Having to use a word here is really pointless, compare

        >> engroup i [1 1 2 2 2 3 4 4 4 4 5 6 7 7]
        == [[1 1] [2 2 2] [3] [4 4 4 4] [5] [6] [7 7]]

        vs.

        >> engroup [1 1 2 2 2 3 4 4 4 4 5 6 7 7]
        == [[1 1] [2 2 2] [3] [4 4 4 4] [5] [6] [7 7]]

        For now, I'm going with the WORD! argument, anyway.
    }

    usage: {
        Simple usage of ENGROUP arranges values into groups of values all equal:

        >> engroup i [1 1 2 2 2 3 4 4 4 4 5 6 7 7]
        == [[1 1] [2 2 2] [3] [4 4 4 4] [5] [6] [7 7]]

        The /OVER refinement specifies over wich value such groups are build.
        This is particular useful when dealing with objects.

        Compare the different grouping in the following examples:

        >> engroup i [#1 #11 #12 #2 #31 #32 #4 #41 #411 #412 #42 #43]
        == [[#1] [#11] [#12] [#2] [#31] [#32] [#4] [#41] [#411] [#412] [#42] [#43]]
        >> engroup/over i [#1 #11 #12 #2 #31 #32 #4 #41 #411 #412 #42 #43] [first i]
        == [[#1 #11 #12] [#2] [#31 #32] [#4 #41 #411 #412 #42 #43]]

        By default, ENGROUP ignores include NONE values.
        But beware, NONE values by design still have an group separating effect:

        >> engroup i reduce [1 1 2 none 2 3 3]
        == [[1 1] [2] [2] [3 3]]

        If by /OVER-ing NONE values are introduced, the by default
        will be ignored, too:

        >> engroup/over i [1 1 2 2 2 2 3 4 4 4 5 5 5] [if odd? i [i]]
        == [[1 1] [3] [5 5 5]]

        Use /ANY to include NONE values:

        >> engroup/any i reduce [1 1 2 2 2 3 none none none none 5 6 7 7]
        == [[1 1] [2 2 2] [3] [none none none none] [5] [6] [7 7]]

        Use /ONLY to group block values as blocks:

        >> engroup i [[1] [1] [2] [2]]
        == [[1 1] [2 2]]
        >> engroup/only i [[1] [1] [2] [2]]
        == [[[1] [1]] [[2] [2]]]

        For convenience, the /AS refinement allows to modify the value "in place".

        >> engroup/as/over i [#1 #1.1 #1.2 #2 #3.1 #3.2 #4 #4.1 #4.1.1 #4.1.2 #4.2 #4.3] [join "" [ i ]] [first i]
        == [["1" "1.1" "1.2"] ["2"] ["3.1" "3.2"] ["4> engroup/as i [1 2 3] [max i 4]
        == [[4] [4] [4]]
        >> engroup/over i [1 2 3 4] [round/to i 2]
        == [[1 2] [3 4]]
        >> engroup/over/as i [1 2 3 4] [round/to i 2] [round/to i 2]
        ==  [[2 2] [4 4]]

        Use /ALL to include NONE values introduced by /AS modifications (usually
        these are not part of the resulting groups):

        >> engroup/as i reduce [1 2 2 3 3 3 4 4 4 4] [all [even? i i]]
        == [[2 2] [4 4 4 4]]
        >> engroup/all/as i reduce [1 2 2 3 3 3 4 4 4 4] [all [even? i i]]
        == [[none] [2 2] [none none none] [4 4 4 4]]
    }

    library: [
        level:          'intermediate
        Platform:       'all
        type:           [function idiom]
        code:           'function
        domain:         'dialects
        license:        'public-domain
        support:        none
        see-also:       none
        tested-under:   [view 2.7.6.3.1 on [WinXP] "CHE"]
    ]
]

engroup: func [
    "Arranges values into groups of all equal values."
    'word    [word!]   "Variable to hold current value"
    data     [series!] "The series to traverse"
    /over    input  [block!] "Value to compare"
    /as      output [block!] "Modify values before grouping"
    /any     "Build groups over NONE values, too."
    /all     "Don't exclude NONE values from groups."
    /only    "Group block values as blocks."
    /into    collector "Where to collect results"
    /local   any* all* do-input value do-output group groups val args prev
][
    any*: any any: get in system/words 'any
    all*: all all: get in system/words 'all

    do-input:  func reduce [[throw] word] any [input  reduce [word]]
    do-output: func reduce [[throw] word] any [output reduce [word]]

    groups: any [all [into collector] copy []]

    until [
        group: copy [] args: 0 prev: none

        foreach input data [
            value: do-input input

            either all [args > 0 previous <> value] [break] [
                args: args + 1
                previous: value
                output: do-output input
                if all [
                    any [any* value]
                    any [all* output]
                ][
                    either only [append/only group output] [append group output]
                ]
            ]
        ]

        unless empty? group [append/only groups group]

        tail? data: skip data args
    ]

    groups
]


            
            
        
Copyright © 2018 Rebol Software Foundation