Mailing List Archive: 49091 messages
  • Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

A "supercharged" Map function.

 [1/16] from: brett:codeconscious at: 22-Jul-2003 14:25


Hi List, I was thinking about the way functions collect their arguments from scripts and then my attention turned to the Map function that many of us have used or created, and then I thought how nice it would be if Map could apply functions with multiple arguments. Then I went over-the-top. I wouldn't be suprised if someone has already got something like this floating around but anyware here it is. See the examples below for what it does. ; Carl Sassenrath's nargs function. nargs: func [ {The number of the function arguments} f [any-function!] ] [-1 + index? any [find first :f refinement! tail first :f]] map: func [ [catch] "Map a function to a series (unsets filtered by default)." mapfunc [any-function! block! none!] "Function or function body block (use VALUE as argument)." series [series! none!] "Series to map." /only "Inserts into result using Only refinement." /filter filterfunc [any-function! block!] "Single argument function or body (use VALUE as argument)." /local num-arg result emit ] [ ; Check arguments for none! if any [none? :mapfunc none? series] [return none] if block? :mapfunc [ mapfunc: func [value [any-type!]] mapfunc] if not filter [ filterfunc: [not unset? get/any 'value]] if block? :filterfunc [ filterfunc: func [value [any-type!]] filterfunc] num-arg: nargs :mapfunc result: make type? series length? series emit: func [value [any-type!]] compose/deep [ if filterfunc get/any 'value [ (pick [insert/only insert] found? only) tail result get/any 'value ] ] while [not tail? series] [ emit do compose [mapfunc (copy/part series num-arg)] series: skip series num-arg ] result ] comment [ ; Shortcut for specifying mapping function. map [value] [1 2 3 4] ; Passing functions as the mapping function. map :add [1 2 3 4] map func [a b] [a / b] [1 2 3 4] ; Default handling of block results. map [reduce [value value * 10]] [1 2 3 4] ; Blocks retained. map/only [reduce [value value * 10]] [1 2 3 4] ; These return none. map none [1 2 3 4] map [value] none map none none ; These return empty blocks. map [] [] map [] [1 2 3 4] map [value] [] ; Custom filter of function results. map/filter [value] [1 2 3 4] [value >= 3] ; Default unset! handling. map [print [value]] [1 2 3 4] ; Default unset! handling overriden. map/filter [print [value]] [1 2 3 4] [true] ; Flattening a tree. sample-tree: [1 [2 [] 3 [4 []]] 5 []] f: func [label subtree] [ append reduce [label] map :f subtree ] map :f sample-tree ; Converts output from Parse-xml to a tree of element names. elt: func [name att content] [ reduce [ name map/filter [ if block? value [map :elt value] ] content [not none? value] ] ] map :elt first third parse-xml xml ] Regards, Brett. --- Website: http://www.codeconscious.com Rebsite: http://www.codeconscious.com/index.r

 [2/16] from: greggirwin:mindspring at: 21-Jul-2003 22:58


Thanks Brett! I'll have to play with this. I have a bunch of not-so-very-nice, not-so-general MAP-ish things that will probably benefit from...being thrown away. :) -- Gregg

 [3/16] from: brett:codeconscious at: 22-Jul-2003 19:32


Don't throw away your MAP-ish things just yet - you may not be expecting that my version evaluates the series elements, which may or may not please you :^) Another version is below that uses do/next to avoid the assumption about the number of arguments. Also, here's a couple more samples to try: map :add [now/date 3 now/date 4 now/date 5] map :parse [ [1] [integer!] [a 2] [word integer!]] map: func [ [catch] {Map a function to a series (evaluates series elements, unsets filtered by default).} mapfunc [any-function! block! none!] "Function or function body block (use VALUE as argument)." series [series! none!] "Series to map." /only "Inserts into result using Only refinement." /filter filterfunc [any-function! block!] "Single argument function or body (use VALUE as argument)." /local result emit value pos tmp ] [ ; Check arguments for none! if any [none? :mapfunc none? series] [return none] if block? :mapfunc [ mapfunc: func [value [any-type!]] mapfunc] if not filter [ filterfunc: [not unset? get/any 'value]] if block? :filterfunc [ filterfunc: func [value [any-type!]] filterfunc] result: make type? series length? series emit: func [value [any-type!]] compose/deep [ if filterfunc get/any 'value [ (pick [insert/only insert] found? only) tail result get/any 'value ] ] series: compose [mapfunc (series)] while [greater? length? series 1] [ tmp: do/next series emit tmp/1 remove/part next series tmp/2 ] result ] Regards, Brett.

 [4/16] from: greggirwin:mindspring at: 22-Jul-2003 8:30


Hi Brett, BH> Don't throw away your MAP-ish things just yet - you may not be expecting BH> that my version evaluates the series elements, which may or may not please BH> you :^) Like I said, I'll have to play with it. :) Thanks for the heads-up though! -- Gregg

 [5/16] from: andrew:martin:colenso:school at: 23-Jul-2003 9:05


Brett wrote:
> map :parse [ [1] [integer!] [a 2] [word integer!]]
Hi, Brett! I think you meant: map :parse [ [1] [integer!] [a 2] [word! integer!]] As for evaluation of the series elements, if one wants the series elements evaluated: map :add reduce [now/date 3 now/date 4] Using 'reduce would seem to be easier and fit better with Rebol? And now what we really need is a 'map shootout! :) Andrew J Martin Attendance Officer & Information Systems Trouble Shooter Colenso High School Arnold Street, Napier. Tel: 64-6-8310180 ext 826 Fax: 64-6-8336759 http://colenso.net/scripts/Wiki.r?AJM http://www.colenso.school.nz/ DISCLAIMER: Colenso High School and its Board of Trustees is not responsible (or legally liable) for materials distributed to or acquired from user e-mail accounts. You can report any misuse of an e-mail account to our ICT Manager and the complaint will be investigated. (Misuse can come in many forms, but can be viewed as any material sent/received that indicate or suggest pornography, unethical or illegal solicitation, racism, sexism, inappropriate language and/or other issues described in our Acceptable Use Policy.) All outgoing messages are certified virus-free by McAfee GroupShield Exchange 5.10.285.0 Phone: +64 6 843 5095 or Fax: +64 6 833 6759 or E-mail: [postmaster--colenso--school--nz]

 [6/16] from: brett:codeconscious at: 23-Jul-2003 11:48


Hi Andrew,
> I think you meant: > map :parse [ [1] [integer!] [a 2] [word! integer!]]
Yep thanks.
> As for evaluation of the series elements, if one wants the series > elements evaluated: > > map :add reduce [now/date 3 now/date 4] > >Using 'reduce would seem to be easier and fit better with Rebol?
My first version evaluated the series as well. In effect the function was evaluating its arguments. I figured then, in that case, I may as well allow expressions to be the series elements. So the input becomes a series of expressions rather than a series of values. So I feel my second version is more useful than my first. This may not be in the spirit of map - I don't know. ...fit better with Rebol? It is a good question. My second version is very similar in capability to reduce - which implies that maybe I've gone to far (no suprise!). I think you're right to imply that map should not overlap with reduce. One reason I can see is that my function cannot process a series of words (they get evaluated). So how then can one write a map function that does not evalate the series elements when the mapping function has multiple arguments - and preferrably do it fairly concisely? If there isn't a way I may as well stick with what I have.
> And now what we really need is a 'map shootout! :)
I think there might have been an informal one some time ago, but I don't recall seeing any versions handle a mapping function with multiple arguments. But yes, improved versions eagerly awaited here! Regards, Brett.

 [7/16] from: andrew:martin:colenso:school at: 23-Jul-2003 14:51


Brett wrote:
> One reason I can see is that my function cannot process a series of
words (they get evaluated).
> So how then can one write a map function that does not evaluate the
series elements when the mapping function has multiple arguments - and preferably do it fairly concisely? If there isn't a way I may as well stick with what I have. Something like this perhaps?
>> a: 1
== 1
>> b: 2
== 2
>> c: 3
== 3
>> d: 4
== 4
>> map [a b c d d c b a] func [x y] [probe reduce [x y]]
[a b] [c d] [d c] [b a] == [a b c d d c b a]
>> map [a b c d d c b a] func [x y z zz] [probe reduce [x y z zz]]
[a b c d] [d c b a] == [a b c d d c b a] Andrew J Martin Attendance Officer & Information Systems Trouble Shooter Colenso High School Arnold Street, Napier. Tel: 64-6-8310180 ext 826 Fax: 64-6-8336759 http://colenso.net/scripts/Wiki.r?AJM http://www.colenso.school.nz/ (I'm using Rebol to print out my source to avoid Rebol formatting religious wars...)
>> source map
map: func [ {Maps or applies the function to all elements of the series.} [catch] Arg1 [any-function! series!] Arg2 [any-function! series!] /Only "Inserts the result of the function as a series." /Full "Doesn't ignore none! values." /local Result Results Function Series][ throw-on-error [ any [ all [ any-function? :Arg1 series? :Arg2 (Function: :Arg1 Series: :Arg2) ] all [ any-function? :Arg2 series? :Arg1 (Function: :Arg2 Series: :Arg1) ] throw make error! reduce [ 'script 'cannot-use rejoin [ {"} mold 'Map " " mold type? :Arg1 {"} ] rejoin [ {"} mold type? :Arg2 {"} ] ] ] Results: make Series length? Series do compose/deep [ foreach [(Arguments :Function)] Series [ if ( either Full [ compose [not unset? set/any 'Result Function (Arguments :Function)] ] [ compose/deep [ all [ not unset? set/any 'Result Function (Arguments :Function) not none? Result ] ] ] ) [ (either Only ['insert/only] ['insert]) tail Results :Result ] ] ] Results ] ]
>> source Arguments
Arguments: func [ "Returns the arguments of the function as a block." F [any-function!] /local Arguments][ Arguments: make block! 2 foreach Argument pick :F 1 [ if refinement? :Argument [ break ] insert tail Arguments :Argument ] Arguments ] DISCLAIMER: Colenso High School and its Board of Trustees is not responsible (or legally liable) for materials distributed to or acquired from user e-mail accounts. You can report any misuse of an e-mail account to our ICT Manager and the complaint will be investigated. (Misuse can come in many forms, but can be viewed as any material sent/received that indicate or suggest pornography, unethical or illegal solicitation, racism, sexism, inappropriate language and/or other issues described in our Acceptable Use Policy.) All outgoing messages are certified virus-free by McAfee GroupShield Exchange 5.10.285.0 Phone: +64 6 843 5095 or Fax: +64 6 833 6759 or E-mail: [postmaster--colenso--school--nz]

 [8/16] from: antonr:iinet:au at: 23-Jul-2003 12:58


Looks good. Is there a direct link to the script? The flattening a tree example shows a good use. I did this recently "by hand" in one of my scripts. Now to remember which program it was... Anton.

 [9/16] from: brett:codeconscious at: 23-Jul-2003 14:15


Andrew wrote:
> Something like this perhaps?
Yes! Did you just cook that up or was it leftovers? :^) Given you are already filtering none and unset maybe it could use the spice of a filter function refinement like I had. No doubt a matter of taste ;^) I especially like the Arguments function. Thanks, Brett.

 [10/16] from: brett:codeconscious at: 23-Jul-2003 14:23


Anton wrote:
> Is there a direct link to the script?
Nope, not yet. All the code is in the emails - just apply clean-script.r :^)
> The flattening a tree example shows a good use. > I did this recently "by hand" in one of my scripts. > Now to remember which program it was...
I've done a few too and was somewhat annoyed at rewriting similar bits of code - which I continue to do. I recently read "Why Functional Programming Matters" and liked the resuablity demonstrated there. Not having been introduced to functional programming concepts until REBOL - such things seem very powerful to me. Regards, Brett.

 [11/16] from: andrew:martin:colenso:school at: 23-Jul-2003 17:11


Rebol Cheff Brett asked:
> Did you just cook that up or was it leftovers? :^)
It was created from the best parts of the last Rebol Map shootout! :)
> Given you are already filtering none and unset maybe it could use the
spice of a filter function refinement like I had. No doubt a matter of taste ;^) I think I'd like that delicate /refinement, Monsieur! ;) Andrew J Martin Attendance Officer & Information Systems Trouble Shooter Colenso High School Arnold Street, Napier. Tel: 64-6-8310180 ext 826 Fax: 64-6-8336759 http://colenso.net/scripts/Wiki.r?AJM http://www.colenso.school.nz/

 [12/16] from: brett:codeconscious at: 23-Jul-2003 22:54


For amusement, below is a function (built with inspiration from Andrew's function) that creates a mapping function.
>> f: mapfun :add >> f [1 2 3 4]
== [3 7]
>> g: mapfun [to word! append to string! value 1] >> g [a b c d]
== [a1 b1 c1 d1] mapfun: func [ [catch] "Returns a function that maps the specified function onto a series (unsets filtered by default)." mapfunc [any-function! block!] "Function or function body block (use VALUE as argument)." /only "Inserts into result using Only refinement." /filter filterfunc [any-function! block!] "Filter specification." "Single argument function or body (use VALUE as argument)." /local args ] [ if block? :mapfunc [ mapfunc: func [value [any-type!]] mapfunc] if not filter [ filterfunc: [not unset? get/any 'value]] if block? :filterfunc [ filterfunc: func [value [any-type!]] filterfunc] args: head clear any [ find first :mapfunc refinement! tail first :mapfunc] func [series [series!] /local _nfv_ _nfr_] compose/deep [ _nfr_: make type? series length? series foreach [(args)] series [ if filterfunc set/any '_nfv_ (:mapfunc) (args) [ (pick [insert/only insert] found? only) tail _nfr_ get/any '_nfv_ ] ] _nfr_ ] ] Regards, Brett.

 [13/16] from: andrew:martin:colenso:school at: 24-Jul-2003 10:26


Brett wrote:
> args: head clear any [ > find first :mapfunc refinement! tail first :mapfunc]
Thanks for that Brett! I had forgotten that the latest Rebol versions return a copy of the function's argument words. Andrew J Martin That which does not kill my 'Map, makes it stronger! :) Attendance Officer & Information Systems Trouble Shooter Colenso High School Arnold Street, Napier. Tel: 64-6-8310180 ext 826 Fax: 64-6-8336759 http://colenso.net/scripts/Wiki.r?AJM http://www.colenso.school.nz/ DISCLAIMER: Colenso High School and its Board of Trustees is not responsible (or legally liable) for materials distributed to or acquired from user e-mail accounts. You can report any misuse of an e-mail account to our ICT Manager and the complaint will be investigated. (Misuse can come in many forms, but can be viewed as any material sent/received that indicate or suggest pornography, unethical or illegal solicitation, racism, sexism, inappropriate language and/or other issues described in our Acceptable Use Policy.) All outgoing messages are certified virus-free by McAfee GroupShield Exchange 5.10.285.0 Phone: +64 6 843 5095 or Fax: +64 6 833 6759 or E-mail: [postmaster--colenso--school--nz]

 [14/16] from: andrew:martin:colenso:school at: 24-Jul-2003 13:45


Brett wrote:
> args: head clear any [ > find first :mapfunc refinement! tail first :mapfunc]
<<quoted lines omitted: 8>>
> _nfr_ > ]
Just been thinking about this, and I thought, what if the 'mapfun copied across the function's refinements and refinement arguments? For example: MyParse: mapfun :parse MyParse/all MyBlockOfStringsAndParseRules MyParse MyBlockOfBlocksAndParseRules Andrew J Martin Attendance Officer & Information Systems Trouble Shooter Colenso High School Arnold Street, Napier. Tel: 64-6-8310180 ext 826 Fax: 64-6-8336759 http://colenso.net/scripts/Wiki.r?AJM http://www.colenso.school.nz/ DISCLAIMER: Colenso High School and its Board of Trustees is not responsible (or legally liable) for materials distributed to or acquired from user e-mail accounts. You can report any misuse of an e-mail account to our ICT Manager and the complaint will be investigated. (Misuse can come in many forms, but can be viewed as any material sent/received that indicate or suggest pornography, unethical or illegal solicitation, racism, sexism, inappropriate language and/or other issues described in our Acceptable Use Policy.) All outgoing messages are certified virus-free by McAfee GroupShield Exchange 5.10.285.0 Phone: +64 6 843 5095 or Fax: +64 6 833 6759 or E-mail: [postmaster--colenso--school--nz]

 [15/16] from: brett:codeconscious at: 24-Jul-2003 15:37


Andrew wrote:
> Just been thinking about this, and I thought, what if the 'mapfun copied > across the function's refinements and refinement arguments? For example: > > MyParse: mapfun :parse
I actually started looking at it then I caught myself before heading into time dimished oblivion. :^) Following your point about reduce and map, this idea made me wonder if map can be broken down into smaller bits and whether I could look at the functionality another way. I've found that I can look at it in another way which I have put it in a new thread "COLLECTing results". Regards, Brett.

 [16/16] from: AJMartin:orcon at: 24-Jul-2003 17:42


Brett wrote:
> (pick [insert/only insert] found? only)
This is a bit quicker and shorter: (pick [insert insert/only] not only) That's because it uses the native 'not not the mezzanine 'found?. Andrew J Martin ICQ: 26227169 http://www.rebol.it/Valley/ http://Valley.150m.com/

Notes
  • Quoted lines have been omitted from some messages.
    View the message alone to see the lines that have been omitted