• Home
  • Script library
  • AltME Archive
  • Mailing list
  • Articles Index
  • Site search
 

World: r4wp

[Rebol School] REBOL School

BrianH
31-Jul-2012
[711x4]
Arnold, what you want to use is FORSKIP. It's like FOR, but with 
series references instead of numbers. No modifying REVERSE required.
Here's an interactive example that you can adapt to your script:
>> a: [1 2 3 4 5 6 7]
== [1 2 3 4 5 6 7]
>> i: find a 3
== [3 4 5 6 7]
>> b: back i
== [2 3 4 5 6 7]
>> f: next i
== [4 5 6 7]
>> forskip b -1 [print first b]
2
1
>> forskip f 1 [print first f]
4
5
6
7
FORSKIP f 1 [...] could be replaced with FORALL f [...] if you like.
The only trick is that you either need extra temp vars for the loop 
variables, or to modify an existing temp var. As a bonus in R3, FORSKIP 
and FORALL are faster than FOR or FOREACH, since no rebinding of 
the code block is necessary.
Steeve
31-Jul-2012
[715]
Faster than foreach ? I beg to differ good sir, in R2 those are mezzanines 
and so are slow like hell.
I would only use foreach, while and until.
But with R3, yeah you're right.
BrianH
31-Jul-2012
[716x2]
Nonetheless, FORSKIP has less overhead than FOR, even on R2. Simpler 
mezz code, no BIND/copy overhead.
You can roll your own code in R2 using WHILE and have it be a little 
faster, but not necessarily a lot faster.
Maxim
31-Jul-2012
[718]
IIRC in all the tests I did, the binding overhead is dwarfed by all 
other considerations. 


a single execution of the block is usually longer than the binding 
process (which is slow, but rebol execution is slower still ;-) . 
 


forskip is by far the slowest loop in R2.  it was several orders 
of magnitude slower than foreach.  and much slower than an until 
block ... not worth it..   I'm happy its native in R3.


obviously, the time spent within the block with relativise the time 
spent in the iteration handling.
BrianH
31-Jul-2012
[719x3]
FOR is slower. You missed comparing FORSKIP to the other mezz loops. 
FOREACH, UNTIL, WHILE, REPEAT and LOOP are native.
That's in R2. In R3, FORSKIP is also native, and faster than FOREACH.
The only mezz loop in R3 is FIND-ALL. It could be ported to R2 as 
well, where it would be slightly slower; actually, I was surprised 
it wasn't there already.
Arnold
1-Aug-2012
[722x2]
Okay, thanks FOR this analysis. Concluding, where I am using R2 (because 
I 'need' VID) I use the foreach forall. The moment R3 is released 
including VID or replacement I will rewrite this code, promise!
On the form validation issue. I managed to get things working as 
I initially intented. Using a Javascript function to change the text 
of the field: 
var changer = document.getElementById('fieldid');
changer.value = fieldvalue;
Where fieldvalue was filled from the cgi data block cgi/fieldname

It was really fun to get this right with all the needed double-quotes 
and curly braces that alle represent comments in rebol as you know 
and generate this from within the well-known function emit: func 
[code] [ repend html code]
I had to add an id tag to all the form fields.
Steeve
1-Aug-2012
[724]
Arnold,  what do you mean by foreach forall ?

Arnold, Forall is the worst choice, especially inside another loop.
Just look at the code of forall.by yourself.
Arnold
1-Aug-2012
[725]
Use the foreach before all other options? :)
Maxim
1-Aug-2012
[726]
in R2... yep  :-)
Arnold
1-Aug-2012
[727x2]
If there is an interest in the basic script with a form with the 
field testing and refilling of field in the case some errors were 
found wrt the input. I make a translated anonimised version showing 
the working.
Maxim, Steeve, in R2 exactly. The script I need these things for 
is slowly progressing now. Lots of testing ideas and making decisions 
for preferred solutions. When it is ready, I will post it on rebol.org
BrianH
1-Aug-2012
[729x2]
Foreach doesn't go in reverse, so you're back to modifying your data. 
If you want to speed up stuff that FORALL does, just look at the 
source. You can inline the FORALL source, drop the error screening 
stuff and simplify the code in the WHILE. You will end up with something 
that can be as fast as the FOREACH, and in some cases faster.
The "in some cases faster" depends on whether you're going in reverse, 
and how much data you have. REVERSE can have a lot of overhead if 
you have a lot of data, so with enough data it would be faster to 
just work in reverse using a WHILE loop, or possibly better yet a 
REPEAT with an index that you subtract.
Arnold
2-Aug-2012
[731]
Time-out! :D

I have enough information to make my script working with reasonable 
speed. After I publish it on rebol.org we can write out a competition 
to have it gain speed ;)
Endo
8-Aug-2012
[732x2]
I wrote a run length encoding function, may be useful for someone 
else too:

rle: func ["Run length encode" b /local v r i j] [
	v: 1 r: copy []
	j: next i: b
	unless empty? b [
		until [
			either all [not tail? j equal? first i first j] [
				v: v + 1 j: next j
			] [
				append r reduce [v first i] v: 1 i: ++ j
			]
			tail? i
		]
	]
	r
]
here is the tests:
>> rle "aaabbcx"
== [3 #"a" 2 #"b" 1 #"c" 1 #"x"]
>>
>> rle []
== []
>>
>> rle ""
== []
>>
>> rle [a]
== [1 a]
>>
>> rle [a a a a a]
== [5 a]
>> rle [a a a a a b b]
== [5 a 2 b]
DocKimbel
8-Aug-2012
[734]
Endo: I think a much faster version could be coded using PARSE.
Endo
8-Aug-2012
[735x2]
But don't I need to write two different set of rules for strings 
and blocks?
And I'm not that good using PARSE, my PARSE expriments usually stuck 
with infinite loops :(
DocKimbel
8-Aug-2012
[737]
I think it should be doable with one set for both datatypes.
BrianH
8-Aug-2012
[738x7]
Not quite. You can almost do it in R3, but you need the QUOTE operation 
for blocks, and QUOTE doesn't work on strings.
Here's a version for R3 parse, with some optimizations:

rle2: funct ["Run length encode" b [series!]] [
	output: copy [] x: none

 r: either any-block? :b [qr: copy [quote 1] [(qr/2: :x) any qr]] 
 [[any x]]
	parse :b [any [pos1: set x skip r pos2: (
		reduce/into [subtract index? :pos2 index? :pos1 :x] tail output
	)]]
	output
]
The biggest overhead there comes from not preallocating the output 
block, so there's some reallocation. I don't know how to estimate 
the size of the output though.
You should really have it be case-sensitive though.
rle2: funct ["Run length encode" b [series!]] [
	output: copy [] x: none

 r: either any-block? :b [qr: copy [quote 1] [(qr/2: :x) any qr]] 
 [[any x]]
	parse/case :b [any [pos1: set x skip r pos2: (
		reduce/into [subtract index? :pos2 index? :pos1 :x] tail output
	)]]
	output
]

>> rle2 [a a A b b c d D d d d]
== [2 a 1 A 2 b 1 c 1 d 1 D 3 d]
I tried to come up with a more optimal R3 version without using parse, 
but I got blocked by case-sensitivity, without considering binding. 
I suppose I could just consider binding too, or unbind the results. 
It also doesn't do structural comparison of functions, either in 
the parse or procedural version.
Can someone come up with an equivalent R2 parse version? Of course 
words aren't case-preserving in R2, nor can they be compared case-sensitively.
DocKimbel
8-Aug-2012
[745]
Here's a R2 solution with same rules for string! and block! series:

rle: func [s [series!] /local out c i][
    out: make block! 1

    parse/case/all s [
        any [
            [end | c: (
                c: either word? c/1 [to-lit-word c/1][c/1]
                i: 1
            )]
           skip
           some [
               c (i: i + 1)
               | (repend out [i c]) break
           ]
       ]
    ]
    out
]

>> rle "aaabbcx"
== [3 #"a" 2 #"b" 1 #"c" 1 #"x"]

>> rle [a a a a a]
== [5 a]

>> rle [a a a a a b b]
== [5 a 2 b]

>> rle [a a A b b c d D d d d]
== [3 a 2 b 1 c 5 d]
BrianH
8-Aug-2012
[746]
Put in an exception for blocks, parens and integers too. In your 
code above, blocks get treated like sub-rules, parens get executed, 
and integers can result in an endless loop.
DocKimbel
8-Aug-2012
[747x2]
Another version of 'rle for R2 that uses two pointers (like your 
R3 version) instead of a counter:

rle: func [s [series!] /local out c pos1 pos2][
    out: make block! 1

    parse/case/all s [
        any [
            [end | c: (
                c: either word? c/1 [to-lit-word c/1][c/1]
            )]
           pos1: skip

           some [c | pos2: (repend out [offset? pos1 pos2 c]) break]
       ]
    ]
    out
]
Brian: I'm just giving a solution that matches the requirements from 
Endo. The block example he provided contains only words.
BrianH
8-Aug-2012
[749x10]
Ah, yeah, I overspecced the R3 version by using QUOTE. Although there's 
a standatd workaround for integers (precede it with 1 1), for any-blocks 
and functions it might be best to do the comparison using REBOL code. 
The R2 equivalent of the IF operation in R3 parse would help here: 
http://www.rebol.net/wiki/Parse_Project#IF_.28condition.29
The replacement for IF (condition) would be something like this: 
(cont: unless condition [[end skip]]) cont
Oh right, I forgot OFFSET? so I recreated it :(
Here's a version of Doc's with all datatypes handled, even unset. 
It even avoids accidentally converting lit-words to words and lit-paths 
to paths:

rle: func [s [series!] /local out pos1 pos2 cont][
	out: make block! 1
	parse/all s [
		any [
			pos1: skip some [
				pos2: skip (cont: if any [
					not-equal? unset? :pos1/1 unset? :pos2/1
					strict-not-equal? :pos1/1 :pos2/1
				] [[end skip]]) cont |
				pos2: (repend out [offset? :pos1 :pos2 :pos1/1]) break
			]
		]
	]
	out
]
There was no point in using parse/case since the comparisons were 
being done by strict-not-equal?, not parse.
Don't know how the speed compares to Endo's original though.
Whoops, it doesn't handle runs of unset values. More adjustment needed.
This version handles unsets too:

rle: func [s [series!] /local out emit pos1 pos2 cont][
	out: make block! 1
	emit: [(repend out [offset? :pos1 :pos2 :pos1/1])]
	parse/all s [
		any [
			pos1: unset! some [pos2: unset! | pos2: emit break] |
			pos1: skip some [
				pos2: unset! :pos2 emit break |
				pos2: skip (
					cont: if strict-not-equal? :pos1/1 :pos2/1 [[end skip]]
				) cont |
				pos2: emit break
			]
		]
	]
	out
]
Slight improvement:

rle: func [s [series!] /local out emit pos1 pos2 cont][
	out: make block! 1
	emit: [(repend out [offset? :pos1 :pos2 :pos1/1])]
	parse/all s [any [
		pos1: unset! any unset! pos2: emit |
		pos1: skip some [
			pos2: unset! :pos2 emit break |
			pos2: skip (
				cont: if strict-not-equal? :pos1/1 :pos2/1 [[end skip]]
			) cont |
			pos2: emit break
		]
	]]
	out
]
For old versions of R2, you might want to change IF STRICT-NOT-EQUAL? 
to UNLESS STRICT-EQUAL?. There used to be a bug in STRICT-NOT-EQUAL? 
in R2, but I forget how far back it was fixed, one of the last couple 
versions.
Endo
9-Aug-2012
[759x2]
The last version crash on string! values:
rle ""
rle "xxx"
both crashes the View 2.7.8.3.1
Here is the benchmark results (execution time for 1.000.000 calls)


>> benchmark [rle [a a a b b c A A a a]] ;BrianH (the old one, not 
the crashing ones)
== 0:00:29.765

>> benchmark [rle [a a a b b c A A a a]] ;endo
== 0:00:32.953