Red by Example - an accessible reference by example

Last update on 20-Dec-2019

series

index     parse     vid     series     draw     help     about     links     contact     
1. Introduction
2. The different types in the series family
3. Initializing series by using copy
3.1. Shallow copy of a series
3.2. Deep copy of a series
3.3. Initialising a series by using copy
4. Positioning in a series
4.1. Positioning in a series - part 1
4.2. Positioning in a series - part 2
5. Selecting individual elements in a series
5.1. Getting an element out of a series
5.3. Using the at function
6. Changing individual elements in a series
7. Looping over a series
8. Some common tasks with series: sort, find, modify etc.
8.1. How to sort a series
8.2. How to find a value in a series
8.3. How to insert a substring into a string
8.4. How to replace a substring with another string
8.5. How to append an item to a series
8.6. How to get a substring from a series
8.7. How to join strings

1. Introduction


The series datatype is a key part of Red.
It provides a unified way of accessing ordered series of elements.

For example, here are some series:

"The cat sat down" ; a string

[33 "animal" 22] ; a block

["The" ["large" "grey"] "cat" "sat" "on" "the" ["cold" "damp"] "floor"] ; a block

Elements in blocks are enclosed in [ ] and are separated by spaces.

Note that blocks can contain variables and elements of mixed types.

We can refer to particular elements via a (1-based) index.
For example, element 2 of each series above is:

#"h" ; a single character, not a string

"animal" ; a string

["large" "grey"] ; a block containing 2 strings

A block can contain other blocks.

Blocks play a key role in the Red language itself.
For example, we can write:

either 4 > 3 [ print "bigger" ] [ print "not bigger" ]

The first block contains 2 elements, being "print" and ""bigger"").
The second block also contains 2 elements, being "print" and ""not bigger"".

In fact, Red can treat the above text either as data, or as code!

Links: block! char! datatype! series! string!

top

2. The different types in the series family


Many Red datatypes belong to the series family:
block! paren! string! file! url! path!
lit-path! set-path! get-path! vector! hash! binary!
image! tag!

So, series functions equally apply to all of those, including strings.

Here are some examples of series types:

DATATYPEEXAMPLE
binary!b: #{FF AA 12}
block!["any" "type" "e.g." 12.23 "Yes" false [ "nested"] ]
file!%important/my-stuff/programs/
get-path!get-path? first [:a/b/c]
hash!; to do by Red team
image!; to do by Red team
lit-path!; to do by Red team
paren!first [(1 + 2 * 3 / 4)] ; inserted in a block to prevent evaluation
path!UK/South-east/London/Westminster
set-path!set-path? first [a/b/c: 10]
string!"Some text"
url!http://www.mypages.home/names.html
vector!v-ages: make vector! [80 18 65]

There is also a related map! (dictionary) type, which is not classified as a series.

Links: datatype! map!

top

3. Initializing series by using copy


When working with series, it is important to understand about copy.

Links: copy

top

3.1. Shallow copy of a series


When we assign a variable to some series, the series VALUES are NOT copied;
instead the REFERENCE to that series is copied into the variable:

red>> days: ["sun" ["mon" "tue"] "wed"]
== ["sun" ["mon" "tue"] "wed"]

red>> new-days: days
== ["sun" ["mon" "tue"] "wed"]

red>> days/1: "fri"
== "fri" ; changed first element of days

red>> new-days/1
== "fri" ; also changed, because new-days is a reference to same days series

When we want a shallow copy, we must use:

red>> days: ["sun" ["mon" "tue"] "wed"]
== ["sun" ["mon" "tue"] "wed"]

red>> new-days: copy days
== ["sun" ["mon" "tue"] "wed"]

red>> days/1: "fri"
== "fri" ; changed first element

red>> new-days/1
== "sun" ; not changed - it is a separate copy

red>> days/2/1: "thu"
== "thu" ; changed "mon" in nested series to "thu"

red>> new-days/2/1
== "thu" ; also changed!!!!!

The last effect is exactly the result of a shallow copy.

All top level elements except nested series are copied by VALUE. That is why
the change to "fri" in the original series did not effect the copied series.

However, for all nested series only the REFERENCE is copied. That is why the last
change (to "thu") in the original series, is visible in the (shallow) copies series.

Links: copy

top

3.2. Deep copy of a series


Normal copying does not copy nested series, but we can use the /deep refinement.
Instead a shallow copy is made, meaning that for nested series only their REFERENCES
are copied!

Example:

red>> s: [11 [222 333 444] 55 66]
== [11 [222 333 444] 55 66] ; contains a nested block as element 2

red>> shallow-copy: copy s
== [11 [222 333 444] 55 66]

red>> deep-copy: copy/deep s
== [11 [222 333 444] 55 66] ; seems equal to shallow-copy isn't it?

red>> shallow-copy/2/3: 888 ; alter the value 444 to 888
== 888

red>> s
== [11 [222 333 888] 55 66] ; original (shallow-copied) series is altered

red>> shallow-copy
== [11 [222 333 888] 55 66]

red>> deep-copy
== [11 [222 333 444] 55 66] ; totally different series

Links: copy

top

3.3. Initialising a series by using copy


As a general rule you should always use copy when you want to initialize a series
from a literal value.

To create e.g. an empty series:

results: copy [] ; an empty block

student-name: copy "" ; an empty string

When you don't do this you will get in trouble. To explain the problem we create a small function:

huh: function [] [
list: ""
append list "huh"
print list
]

Now we run this function a few times and see what happens:

red>> huh
huh

red>> huh
huhhuh

red>> huh
huhhuhhuh

What the heck??! Here is what happens:

1. list is a local variable; this is the default in a function;
note that in a func we need to explicitly declare the variable list
to be local with the /local option.

2. when we say:
list: ""
the first time we call the function we ask Red to do the following:
- allocate some fresh storage for us
- initialize that storage with an empty string
- ASSIGN that storage PERMANENTLY to the variable list

3. when we call the function again:
list: ""
list will NOT be initialized again; it just has the same contents
as when the function terminated the last time.

4. so, when we alter such series after initializing, the contents will
be the same as when we left it in the previous function call

So again, initialize series variables from literals ALWAYS with a copy, UNLESS
you wish to use this as a FEATURE.

As a side note:
The programming language C has STATIC variables (inside functions) which behave
exactly the same. The contents of such variable is kept and can be used in later
function calls.

Links: append copy func function

top

4. Positioning in a series


We can walk through series in a forward and backward manner.

top

4.1. Positioning in a series - part 1


A series has a head, a tail and a current index.

When we initially create our series:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

Effectively we have this:

- the head of the days series always points to the first element
if there is one. Otherwise head an tail are equal.

- variable days currently is at the head of the days series

- the tail always points just beyond the last element of a series

The days variable is kind of a 2-in-1 variable:

- it is the name of the series

- it is also an index into that same series

The days variable initially is set to 1 (index is at the head).

With index? we can request the index where the variable days currently points to.

Explore the initial state of days:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

red>> first days
== "sun"

red>> index? days
== 1 ; value 1 means we are at the head position

red>> head? days
== true ; always in head position if index = 1

red>> tail? days
== false

This is as we might expect. The head? and tail? functions tell us if the
index (which is held in the variable days) is currently at the head or
at the tail of the days series.

Now we will use the next function to increment the index by 1 and explore the state:

red>> days: next days
== ["mon" "tue" "wed"]

red>> days
== ["mon" "tue" "wed"]

red>> first days
== "mon" ; first operates relative to where days currently points to

red>> index? days
== 2 ; index now points to "mon"

red>> head? days
== false ; index = 2 (to be in head position index should have been 1)

red>> tail? days
== false

The index is now 2, but the first value is now "mon", showing that the functions:
first, second, third etc. work relative to the current index value.

If we position to the tail (e.g. by using next several times) we see:

red>> days: next next next days
== [] ; days now points beyond the last element

red>> first days
== none ; there is no first element - days points to empty series

red>> index? days
== 5 ; in the original series there are 4 elements, so we are beyond the last

red>> head? days
== false

red>> tail? days
== true ; because index is just beyond the last element

The possibility of going beyond the head or the tail is covered later.

Links: head head? index? next tail tail? first second third fourth fifth

top

4.2. Positioning in a series - part 2


We can position the index at the head or the tail:

red>> days: head days
== ["sun" "mon" "tue" "wed"]

red>> days: tail days
== []


We can use the back function to do the opposite of next (decrement the index):

red>> days: back days
== ["wed"]

We can use the skip function (with a positive or negative argument) to move the index
forward or backward relative to the current index:

red>> days: skip days -2
== ["mon" "tue" "wed"]

red>> days: skip days 1
== ["tue" "wed"]

NOTE:
When an attempt is made to move the index to before the head or beyond the tail,
no errors are produced. Red then sets the index to the head or the tail.

Links: back head skip tail

top

5. Selecting individual elements in a series


Given the series:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

Initially, the days variable is at the head (index = 1).

To access individual items, there are several possibilities.

Links: head

top

5.1. Getting an element out of a series


Example using the pick function:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

red>> chosen: pick days 3
== "tue"

red>> n: 4
== 4

red>> chosen: pick days n
== "wed"

The pick function works relative to the index variable days:

red>> days: next days
== ["mon" "tue" "wed"]

red>> index? days
== 2

red>> pick days 3
== "wed" ; relative to the index days points to

red>> n: 4
== 4

red>> pick days n
== none ; we now are at tail position

Here pick is used to access a nested series, using variables:

red>> s: [1 2 [33 99] 5]
== [1 2 [33 99] 5]

red>> m: 3
== 3

red>> n: 2
== 2

red>> pick pick s m n
== 99

We can also use path notation:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

red>> days/2 ; 2 is the number of the element we want
== "mon"

A path can also be used to change an element, as in:

red>> days/2: "fri"
== "fri"

red>> days
== ["sun" "fri" "tue" "wed"]

Also using a path using a variable:

red>> s: [1 2 3 4]
== [1 2 3 4]

red>> i: 3
== 3

red>> s/:i: 33 ; note that we need get-word notation here
== 33

red>> s
== [1 2 33 4]

Path notation can also be used to access nested series, as in:

red>> s: [1 2 [33 34 35] 4]
== [1 2 [33 34 35] 4] ; a nested series

red>> i: 3 ; used to point to element in top-level series -> [33 34 35]
== 3

red>> j: 2 ; used to point to element in second-level series -> 34
== 2

red>> s/:i/:j: 124
== 124

red>> s
== [1 2 [33 124 35] 4] ; second-level series has changed indeed

We can use the pre-defined functions: first, second, third, fourth and fifth
to access an element (note that these functions operate relative to the index the series
currently points to):

red>> s: [11 12 13 14 15 16]
== [11 12 13 14 15 16]

red>> first s
== 11

red>> second s
== 12

red>> third s
== 13

red>> fourth s
== 14

red>> fifth s
== 15

Links: index? path! pick first second third fourth fifth

top

5.3. Using the at function


This function returns a reference to the rest of the series starting with the element
its argument points to:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

red>> at days 3
== ["tue" "wed"]

This function also works relative to the index of a series.

Links: at

top

6. Changing individual elements in a series


Example:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

red>> poke days 2 "fri"
== "fri" ; note that poke returns the new element value

red>> days
== ["sun" "fri" "tue" "wed"]

To change nested elements we must use path notation as shown earlier on this page

Links: poke

top

7. Looping over a series


Making use of Red's powerful series functions will minimize your use of loops,
but should you need to do this, here is an example using foreach:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

red>> foreach day days [ print day ]
sun
mon
tue
wed

The variable day takes each value in turn.

Note that foreach does consider the index where days currently points to!

Links: foreach

top

8. Some common tasks with series: sort, find, modify etc.


Here are some typical tasks that you might need to do on series.
The details of the individual series functions are covered separately.

top

8.1. How to sort a series


Example:

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

red>> sort days
== ["mon" "sun" "tue" "wed"]

Note that the original series is modified!

Links: sort

top

8.2. How to find a value in a series


The find function returns a reference to the series, at the found position

red>> days: ["sun" "mon" "tue" "wed"]
== ["sun" "mon" "tue" "wed"]

red>> find days "tue"
== ["tue" "wed"]

Links: find

top

8.3. How to insert a substring into a string


Example:

red>> letters: "abcdefg"
== "abcdefg"

red>> insert find letters "d" "XX"
== "defg"

red>> letters
== "abcXXdefg"

Links: insert

top

8.4. How to replace a substring with another string


Example:

red>> letters: "abcdefgabcdefgabcdefg"
== "abcdefgabcdefgabcdefg"

red>> replace letters "cd" "CD"
== "abCDefgabcdefgabcdefg" ; only the first occurrence is changed

red>> letters
== "abCDefgabcdefgabcdefg"

In order to replace all occurrences, do:

red>> letters: "abcdefgabcdefgabcdefg"
== "abcdefgabcdefgabcdefg"

red>> replace/all letters "cd" "CD"
== "abCDefgabCDefgabCDefg"

red>> letters
== "abCDefgabCDefgabCDefg"

Links: replace

top

8.5. How to append an item to a series


Example:

red>> s: [1 2 3]
== [1 2 3]

red>> append s 4
== [1 2 3 4]

red>> s
== [1 2 3 4]

Links: append
top

8.6. How to get a substring from a series


Here we use a string value, but code works with any type, of course.

>> s: "ABCDEFGH"
== "ABCDEFGH"
>> copy/part at s 2 4 ;-- start at position 2, get 4 items
== "BCDE"

top

8.7. How to join strings


If you want to add a string to the end of an existing string, you can use
append, as above. If you want to join literals, calculations etc, in a more
flexible way, consider rejoin, as in

>> shape: " square "
== " square "
>> side: 6
== 6
>> rejoin ["The" shape "is " side * side " sq Units"]
== "The square is 36 sq Units"

Each item is evaluated (reduced), then joined.