ArrayList

To use the ArrayList type, you need to import the collection package:

import std.collection.*

ArrayList is a generic type, so Cangjie uses ArrayList<T> to denote it, where T is the element type. T can be any type or a type variable.

ArrayList has excellent capacity expansion capability and is applicable to scenarios where elements must be stored sequentially and be directly accessible by their position in the sequence, as in an Array, but in addition need to be frequently added to, and removed from, the sequence. Compared with Array, ArrayList can add and remove elements in place, without copying the elements left intact to a newly created instance.

ArrayList is a reference type: all references to the same ArrayList instance share the same elements and are modified in a unified manner. See Modifying an ArrayList for details.

var a: ArrayList<Int64> = ... // ArrayList whose element type is Int64
var b: ArrayList<String> = ... // ArrayList whose element type is String

As generic types in Cangjie are invariant, two instantiations of ArrayList with different element types are themselves different, incompatible types, even if their element types are in a subtype relationship. Therefore, values of those two types cannot be "cross-assigned" to variables.

Therefore, the following continuation of the last example is invalid:

b = a // Error: Type mismatch

In Cangjie, you use a constructor to create an ArrayList with a particular element type. The different constructors enable you to pre-allocate storage and/or initialize the elements.

// Create an empty ArrayList of strings:
let a = ArrayList<String>()

// Create an ArrayList of strings, pre-allocating space for 100 elements:
let b = ArrayList<String>(100)

// Create an ArrayList of three Int64 integers, containing elements 0, 1, and 2:
let c = ArrayList<Int64>([0, 1, 2])

// Populate a freshly created ArrayList with elements of another collection "c":
let d = ArrayList<Int64>(c)

// Create an ArrayList of two strings, initialized by the specified rule function:
let e = ArrayList<String>(2, {x: Int64 => x.toString()})

Accessing ArrayList Members

When you need to access each element of an ArrayList, you can use a for-in loop expression to traverse it.

import std.collection.*

main() {
    let list = ArrayList<Int64>([0, 1, 2])
    for (i in list) {
        println("The element is ${i}")
    }
}

Compiling and executing the above code outputs the following information:

The element is 0
The element is 1
The element is 2

When you need to know the number of elements in an ArrayList, you can use the size property to obtain that information.

import std.collection.*

main() {
    let list = ArrayList<Int64>([0, 1, 2])
    if (list.size == 0) {
        println("This is an empty arraylist")
    } else {
        println("The size of arraylist is ${list.size}")
    }
}

Compiling and executing the above code outputs the following information:

The size of arraylist is 3

When you want to access a single element at a specific position, you can use the index operator [ ]. The type of the index expression must be Int64. The index of the first element of a non-empty ArrayList is zero. You can access any element of an ArrayList from 0 to the position of the last element, which is one less than the value of the size property of the ArrayList. Using a negative number or a number greater than or equal to size as an index triggers a run-time exception.

let a = list[0] // a == 0
let b = list[1] // b == 1
let c = list[-1] // Runtime exception

ArrayList also supports the syntax of Range in indices. For details, see section Array.

Modifying an ArrayList

You can use the index operator [ ] to modify an ArrayList element at a certain position:

let list = ArrayList<Int64>([0, 1, 2])
list[0] = 3

ArrayList is a reference type. When an ArrayList is used as an expression, no copy is created. All references to the same ArrayListinstance share the same data. Therefore, modifications of an ArrayList instance element affect all references to that instance:

let list1 = ArrayList<Int64>([0, 1, 2])
let list2 = list1
list2[0] = 3
// list1 contains elements 3, 1, 2
// list2 contains elements 3, 1, 2

To add a single element to the end of an ArrayList, use the append member function. If you want to add multiple elements to the end at the same time, you can use the appendAll member function. That function can accept a value of another collection type (for example, Array), provided its element type is the same.

import std.collection.*

main() {
    let list = ArrayList<Int64>()
    list.append(0) // list contains element 0
    list.append(1) // list contains elements 0, 1
    let li = [2, 3]
    list.appendAll(li) // list contains elements 0, 1, 2, 3
}

You can use the insert and insertAll member functions in a similar manner to insert a specified element, or all elements of a collection with the same element type, at a specific position. The size of the ArrayList first increases accordingly and the elements at and to the right of the specified index are moved towards the end of the resized ArrayList to make space for the element(s) being inserted.

let list = ArrayList<Int64>([0, 1, 2]) // list contains elements 0, 1, 2
list.insert(1, 4) // list contains elements 0, 4, 1, 2

To delete an element from an ArrayList, you use its remove member function, specifying the index of the element to be deleted. The subsequent elements, if any, are then moved to fill the space, and the size property is decremented.

let list = ArrayList<String>(["a", "b", "c", "d"]) // list contains the elements "a", "b", "c", "d"
list.remove(1) // Delete the element at index 1, now the list contains elements "a", "c", "d"

Increasing the Size of an ArrayList

Each ArrayList reserves a certain amount of memory to hold its contents. When we add elements to an ArrayList so that its reserved capacity would be exceeded, the ArrayList allocates a larger memory area and copies all its elements to that new memory. This growth strategy means that operations adding elements have higher performance costs when they trigger such reallocation, but as the reserved memory of the ArrayList increases, such operations occur less frequently.

If you know how many elements will be added to an ArrayList, you can have it pre-allocate a sufficient amount of memory before adding elements to avoid intermediate reallocation, which improves performance.

import std.collection.*

main() {
    let list = ArrayList<Int64>(100) // Allocate space at once
    for (i in 0..100) {
        list.append(i) // Does not trigger reallocation of space
    }
    list.reserve(100) // Prepare more space
    for (i in 0..100) {
        list.append(i) // Does not trigger reallocation of space
    }
}