Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rebol COLLECT: keep the order of the source data in a loop

There is some source data, like [1 2 3 4 "a" "b" "c" "d"], four items as a group. I want to extract some data at specific positions, such as the first, third, and fourth from each group.

There is my code:

data: [1 2 3 4 "a" "b" "c" "d"]
output: copy []
foreach [p1 p2 p3 p4] data [ collect/into [keep p1 keep p3 keep p4] output ]
probe output    ;;the output is ["a" "c" "d" 1 3 4]

But what I really want is [1 3 4 "a" "c" "d"]. How can I keep the order?

like image 691
Wayne Cui Avatar asked Mar 20 '23 14:03

Wayne Cui


2 Answers

All functions in Rebol which use the /into option use insert semantics. We added that option to allow incremental building without needing to make as many intermediate series, and to allow you to pick the type of target series, preallocate, and all sorts of other power user tricks. The /into option uses insert semantics because insert doesn't lose as much information as append.

Let's take your example but just use collect:

data: [1 2 3 4 "a" "b" "c" "d"]
output: collect [
    foreach [p1 p2 p3 p4] data [ keep p1 keep p3 keep p4 ]
]
probe output

That's the kind of simple code that collect is supposed to make it easier to write. But it's a bit slow, so let's try optimizing it a bit by using /into:

data: [1 2 3 4 "a" "b" "c" "d"]
output: copy []
foreach [p1 p2 p3 p4] data [
    output: collect/into [keep p1 keep p3 keep p4] output
]
probe head output

That is the standard model for /into code, and will make things output in the order you want. But it really doesn't have any advantages over using regular collect, since you don't preallocate the target block. This will save on reallocations:

data: [1 2 3 4 "a" "b" "c" "d"]
output: make block! 0.75 * length? data
foreach [p1 p2 p3 p4] data [
    output: collect/into [keep p1 keep p3 keep p4] output
]
probe head output

But collect itself can be a little slow because it's not native; it's mostly a convenience function. Fortunately, we have faster native functions, which use /into the same way:

data: [1 2 3 4 "a" "b" "c" "d"]
output: make block! 0.75 * length? data
foreach [p1 p2 p3 p4] data [ output: reduce/into [p1 p3 p4] output ]
probe head output

No non-native functions used there, this should be pretty fast.

like image 71
BrianH Avatar answered May 15 '23 21:05

BrianH


For a reason unknown to me, collect uses insert internally, so it inserts data at the beginning, not append them at the end. I hope someone can give an explanation why it's this way. Meanwhile, you can use good old repend to do the job:

data: [1 2 3 4 "a" "b" "c" "d"]
output: copy []
forskip data 4 [repend output [data/1 data/3 data/4]]
probe output    ;; [1 3 4 "a" "c" "d"]

Also, it's better to use forskip instead of foreach in this case, as you do not have to define variables and access just by index.

like image 39
rebolek Avatar answered May 15 '23 22:05

rebolek