Data Structures

_images/unicon.png

Index Unicon

One of the features that elevates Unicon from a high level language to a very high level language is the range of structured data types.

Unicon mutable data types

List (arrays)

Unicon lists are mutable, ordered collections of data. Each element can be any datatype. Lists are created with square bracket syntax, or the list function. list() accepts an optional size parameter, followed by an optional default initial value.

&null is the default initial value if not specified.

Appending to a list is supported with the ||| (list concatenation) operator, which can be augmented with assignment (as is the case for almost all Unicon operators).

List functions include stack and queue like operations, along with arbitrary positioning.

  • push, pop (front of the list)
  • put, pull (end of the list)
  • insert(L, i, x) will insert x at position i
  • delete (L, i,...) will delete the elements at position i and other given indexes.

Subscript indexing of a list is supported with square bracket syntax. List[index]. This is where Unicon lists, and arrays look very similar. Lists are one-dimensional vectors. Subscripting will fail if the requested element(s) are out of current list size range.

Create new list sections with square bracket and colon syntax, L[start:end]. This syntax creates new lists.

Subscript range access creates new lists. List sections are not variable; the original list is not modifiable using sections. Where as Groceries[1] := "Milk" modifies the Groceries list, Groceries[1:3] := "Milk" is invalid; the Groceries[1:3] section expression is not a variable and assignment does not apply.

The get function will remove and return multiple elements from the head of a list.

list comprehension

While the normal list creation syntax is [v1, v2,...], Unicon also features list comprehension. The syntax is [: expr :], all values from the expression become values in the returned list.

#
# list-operations.icn, some simple List examples
#
link fullimag

procedure main()
    local L1, L2
    # L1 is 5 elements of 0
    L1 := list(5, 0)
    L2 := ["a", "b", "c", "c", 1, 1.0, ["sublist"]]

    # lists are ordered, mutable structures
    write(fullimage(L1), " size of L1: ", *L1)
    write(fullimage(L2), " size of L2: ", *L2)

    # Indexing
    write("\nL2[3]: ", L2[3])
    write("L2[1:3]: ", fullimage(L2[1:3]))

    # Concatenation
    write("\nL1 ||| L2")
    write(fullimage(L1 ||| L2))

    # Membership
    if member(L1 ||| L2, "d") then write("\nd is in the list")
   
    # Insertion
    write("\nInsertion")
    write(fullimage(insert(L2, 1, "first")))

    # List comprehensions
    write("\nComprehension with [: 1 to 10 :]")
    write(fullimage([: 1 to 10 :]))
    write("Comprehension with [: 1 to (1 to 5) :]")
    write(fullimage([: 1 to (1 to 5) :]))
end 

examples/list-operations.icn

prompt$ unicon -s list-operations.icn -x
[0,0,0,0,0] size of L1: 5
["a","b","c","c",1,1.0,<2>["sublist"]] size of L2: 7

L2[3]: c
L2[1:3]: ["a","b"]

L1 ||| L2
[0,0,0,0,0,"a","b","c","c",1,1.0,<2>["sublist"]]

Insertion
["first","a","b","c","c",1,1.0,<2>["sublist"]]

Comprehension with [: 1 to 10 :]
[1,2,3,4,5,6,7,8,9,10]
Comprehension with [: 1 to (1 to 5) :]
[1,1,2,1,2,3,1,2,3,4,1,2,3,4,5]

Set

Unicon Set data is an unordered, mutable data structure that follows set theory. Sets are initialized with the Set function.

Union, intersection, difference, insertion and membership testing are all supported. Set elements are unique within a set.

Cset is an internally efficient form of character sets.

#
# set-operations.icn, some simple Set data examples
#
link fullimag

procedure main()
    S1 := set("a", "b", "c", "c", "d")
    S2 := set("d", "e", "f", "g", "h")

    # sets have a uniqueness propery, "c" is only held once
    write("Uniqueness")
    write(fullimage(S1))
    write(fullimage(S2))

    # Union
    write("\nUnion S1 ++ S2")
    write(fullimage(S1 ++ S2))

    # Intersection
    write("\nIntersection S1 ** S2")
    write(fullimage(S1 ** S2))

    # Difference
    write("\nDifference S1 -- S2")
    write(fullimage(S1 -- S2))

    write("Difference S2 -- S1")
    write(fullimage(S2 -- S1))

    # Membership
    if member(S1 ** S2, "d") then write("\nd is in both sets")
   
    # Insertion
    write("\nInsertion")
    write(fullimage(insert(S1, "i")))
end 

examples/set-operations.icn

prompt$ unicon -s set-operations.icn -x
Uniqueness
set("a","b","c","d")
set("d","e","f","g","h")

Union S1 ++ S2
set("a","b","c","d","e","f","g","h")

Intersection S1 ** S2
set("d")

Difference S1 -- S2
set("a","b","c")
Difference S2 -- S1
set("e","f","g","h")

d is in both sets

Insertion
set("a","b","c","d","i")

Table

Unicon tables are associative arrays, key-value pairs. Keys can be any type, values can be any type. Created with the Table function, access is by square bracket subscripting (where the subscript is a key specifier).

insert, delete are supported.

Structure operators such as * size and ? random element operators (returning a random table value, not a key) are also built to work with tables.

The generate elements operator, generates values not keys.[1] The function key(T) generates the keys of a table.

Subscripted access will return a default value if lookup does not find the key. The default value is the optional argument of the Table declaration function, or &null.

The member function succeeds if the given key exists in the table, or fails otherwise.

#
# table-operations.icn, some simple table() examples
#
link ximage

procedure main()
    T1 := table()              # the default default is &null
    T2 := table(0)             # default element is 0

    # tables are mutable, key value pair structures
    # accessing an unknown key returns the table default value
    # setting an unknown key inserts the key-value pair

    write("T1[\"unknown\"]: ", image(T1["unknown"]))
    write("T2[\"unknown\"]: ", image(T2["unknown"]))

    T1["unknown"] := "now known"
    write("T1[\"unknown\"]: ", image(T1["unknown"]))

    # T2 elements default to 0, math is ok for unknown elements
    write("T2[\"unknown\"] + 42: ", image(T2["unknown"] + 42))

    write("\nximages:")
    write(ximage(T1))
    write(ximage(T2))
    
    # Access is by square bracket operator
    # keys can be any type, values can be any type
    T2[3] := "42"
    T2["3"] := 42
    write("\nElements:\nT2[3]: ", image(T2[3]))
    write("T2[\"3\"]: ", image(T2["3"]))

    # Size, note: T2["unknown"] was not assigned during the computation 
    write("size of T1: ", *T1)
    write("size of T2: ", *T2)

    # element generator gives values
    write("\nT2 values")
    every write(image(!T2))

    # key() function, generates the keys
    write("\nT2 keys")
    every write(image(key(T2)))
    write(ximage(T2))
    
    # Membership
    if member(T2, 3) then write("\ninteger 3 is a key in T2")

    # removal
    v := delete(T2, 3)
    write("\ndelete T2[3], return value is: ", type(v))
    # membership test will now fail, no output written
    if member(T2, 3) then write("\ninteger 3 is a key in T2")
    write("T2 keys after delete")
    every write(image(key(T2)))

    # table values can also be functions
    fT := table()
    fT["add"] := adder
    operation := "add"
    # add two values, invoked from table key
    write("\noperation ", operation, " returns ", fT[operation](1,2))
    write(ximage(fT))
end

#
# adder function
#
procedure adder(a,b)
    return a + b
end 

examples/table-operations.icn

prompt$ unicon -s table-operations.icn -x
T1["unknown"]: &null
T2["unknown"]: 0
T1["unknown"]: "now known"
T2["unknown"] + 42: 42

ximages:
T1 := table(&null)
   T1["unknown"] := "now known"
T2 := table(0)

Elements:
T2[3]: "42"
T2["3"]: 42
size of T1: 1
size of T2: 2

T2 values
"42"
42

T2 keys
3
"3"
T2 := table(0)
   T2[3] := "42"
   T2["3"] := 42

integer 3 is a key in T2

delete T2[3], return value is: table
T2 keys after delete
"3"

operation add returns 3
T6 := table(&null)
   T6["add"] := procedure adder

Todo

more on tables

[1]The fact that the ! operator generates values and not keys for table data was deemed an early Icon language design mistake. Before Icon v7, you needed to convert a table to a paired list to get at keys. The key function was added to get around this design flaw. Key value pair associative data tables were originally in SNOBOL. This extremely useful Unicon data structure has a long history,

Record

Records are fixed length[2], named field structures, created with a globally named initializer defined with the record reserved word at compile time. Unicon record structures can also be defined at runtime with the constructor function.

Compile time record definitions occur outside of procedures in source code. The initializer is global to all procedures within a compile unit. constructor definitions are contained within the same scope as the variable used to store the return value from the function.

record data plays a background role in the object oriented features of Unicon, but programmers don’t need to worry about that, details managed by the compiler.

#
# record-operations.icn, demonstrate records
#
link ximage

# record initializers are in the global name space
record sample(field1, field2, field3)

procedure main()
    # records are created by the initializer
    R1 := sample("value1", "value2", 3)

    # record field access uses the dot operator
    write("R1.field1: ", R1.field1)
    write("R1.field2: ", R1.field2)
    write("R1.field3: ", R1.field3)

    # Unicon also allows runtime creation of record constructors
    Rcon := constructor("runtime", "f1", "f2", "f3")

    # The new "runtime" initializer is now available as Rcon
    R2 := Rcon("v1", "v2", R1)

    write("\nR2.f1: ", R2.f1)
    write("R2.f2: ", R2.f2)
    # like all Unicon structures, fields can be arbitrarily nested
    write("R2.f3.field1: ", R2.f3.field1)
    write("type of R2: ", type(R2))
    write("type of R2.f3: ", type(R2.f3))
end 

examples/record-operations.icn

prompt$ unicon -s record-operations.icn -x
R1.field1: value1
R1.field2: value2
R1.field3: 3

R2.f1: v1
R2.f2: v2
R2.f3.field1: value1
type of R2: runtime
type of R2.f3: sample
[2]Records are fixed length in terms of always having the same number of named fields. The field elements themselves can be any data type, of variant sizes. This is not the same as a COBOL fixed length record, which always have the same number of bytes in each field and in each record hierarchy.

Index | Previous: Datatypes | Next: Expressions