Exordium
A fixed (or fixed-size, fixed-length) array is an array that has a max amount of items. Such arrays are used when the programmer knows how many elements an array should hold ā such as the number of enemy spaceships a game should display on-screen at once or the value of an 8-bit character.
Some languages, such as C, Objective-C, and C++,Ā use fixed arrays, while more modern languages use arrays that arenāt set in size. In a language that uses fixed arrays, once you set the size of the array āĀ it does not change.Ā For example, if a fixed array is initialized with 15 elements, the array can not store 16 elements. Though the array is incapable of growth, it is capable of mutation. With the first 15 elements, if the 14th element needed to be changed, it could be. If an element needs to be deleted, it can be; the fixed-size of 15 can hold less than 15 elements; it just canāt hold more.
Below is an array in C++ that holds 15 integers. The elements of the array are placed in a contiguous block of memory:
int foo [15];
When working with fixed arrays, itās important to be precise and understand whyĀ youāre using this type of array. They leave room for overflow errors and are generally not a flexible data structure. That said, the good thing about these types of arrays is that they are predictable and fast. You know what to expect regarding the number of elements stored in the array and how large the array is in memory.
Fixed Array Operations
Fixed arrays should be able to:
- Append new elements to the end
- Insert new elements at the beginning and in the middle
- Delete elements
- Look up elements by index
- Count the size of the array
Appending an element to the end of a fixed array is easy as long as the array isnāt full. Again, the size of a fixed array does not change after it is set. This operation has a time complexity ofĀ O(1). Another quick operation is finding an element by the index, also aĀ constant time complexity. To continue on the topic of time complexity, inserting and deleting are expensive with a fixed array. If an element is being inserted in the middle or beginning of a fixed array, all the elements to the right of the new element must be moved up a position in memory by 1.
To add the number 5 to a fixed array:
Something to note;Ā if there is code that references an object of the array using an index, and a new element is added into the middle of the array, the indexes no longer reference the correct objects.
Furthermore, deleting an element from the array is the same process, but reversed. The elements are moved from one position to the left instead of the right.
To delete the number 5 from the same array:
Both inserting and deleting elements from a fixed array results in linear time complexity āĀ O(n).
How to Build a Fixed Array
Here, Swift is used to building a fixed array, though the concept can be used in any language.
The Fundamental Properties
- The first thing to do is create the fixed array, its basic properties, and an initializer. The array needs the following properties:
- A max size to ensure it doesnāt grow past what the use case needs. A default value (this will be used to add an item to the array when needed).
- An internal array (in this case, itās aĀ Swift array).
- The count (which is updated each time an element is removed or added to the array).
- Two public properties that return information. The first being the elements within the array, and the second being the length of the array.Ā
Ā is not directly used to return the arrayās length (e.g.,count
) because it is private. It is private because in this use case, tracking the number of elements in an array with a counter and then reading theĀfixedArray.count
Ā property is faster than directly calling the internalĀcount
Ā method on the array; which traverses through each element, then returns the number of elements in the array.count
struct FixedArray<T> {
private var maxSize: Int
private var defaultValue: T
private var array = [T]()
private var count = 0
public var elements: [T] {
return array
}
public var length: Int {
return count
}
public init(maxSize: Int, defaultValue: T) {
self.maxSize = maxSize
self.defaultValue = defaultValue
array = .init()
}
}
The initializer sets the max amount of elements to be allowed in the array, along with the ādefault valueā of the array.Ā
defaultValue
Ā is used to add a default object to the array when appending new objects. This will be seen in theĀ insert
Ā method.The other thing to add in the Swift implementation of a Fixed Array is a custom subscript. In Swift, you can addĀ custom subscriptsĀ to Structs, Classes, and Enumerations in order to access the elements of a collection, list, or sequence.
struct FixedArray<T> {
private var maxSize: Int
private var defaultValue: T
private var array = [T]()
private var count = 0
public var elements: [T] {
return array
}
public var length: Int {
return count
}
public init(maxSize: Int, defaultValue: T) {
self.maxSize = maxSize
self.defaultValue = defaultValue
array = .init()
}
subscript(index: Int) -> T {
assert(index >= 0)
assert(index < count)
return array[index]
}
}
When a fixed array is initialized, it is empty. This can be seen by using the
elements
Ā andĀ length
Ā properties.var array = FixedArray<Int>(maxSize: 6, defaultValue: 0)
array.elements
// []
array.length
// 0
Adding Functionality to the Array
As noted in theĀ Fixed Array OperationsĀ section of this article, fixed arrays should be able to:
- Append new elements to the end.
- Insert new elements at the beginning and in the middle.
- Delete elements.
- Look up elements by index.
- Count the size of the array.
It is important to note that any time an element is added or removed, the count variable must be updated in accordance with this action.
The first custom method will be namedĀ
appendToEnd()
Ā and it will do just that ā add an element to the end of the array. Before the method adds the element, it usesĀ assert
Ā to make sureĀ count
Ā is less thanĀ maxSize
. If count
Ā is greater than the total amount of allowed elements, no more elements can be added to the array. To learn more about using assert in Swift,Ā check out the official Apple Documentation.// Append to end of array
public mutating func appendToEnd(element: T) {
assert(count < maxSize)
array.append(element)
count += 1
}
Next, inserting elements in the middle and beginning of the array. There are a few things to keep in mind when writing this method.
The array has the chance to hold different variations when adding new elements. It may be empty; it may be full; it may be half full, etc. Also, the index where an element needs to be added may be different each time the method is used.
In this instance, the method checks for the following cases then proceeds to handle the insertion accordingly:
- TheĀ
Ā is 0, and theĀindex
Ā property is 0. The array is empty, and the element needs to be inserted at the beginning, as the very first element in the array.count
- TheĀ
Ā is equal toĀcount
Ā and theĀmaxSize
Ā is 0 OR theĀindex
Ā is equal toĀcount
Ā and theĀmaxSize
Ā is not equal to 0. The array is full, and the element needs to be inserted at the beginning, or somewhere in the middle or end. This ensures that no new elements are added to the array when trying to insert them when the array is full.index
- TheĀ
Ā is 0, andĀindex
Ā is not equal toĀcount
. In this case, the requested element needs to be inserted at the beginning, shifting the current elements to the right in order to make room for the new element. This is accomplished by appending a new default element to the end of the array, traversing through the elements starting at index 1, moving through the reversed version of the elements in the array, and moving them in one space to the right. Remember that the value ofmaxSize
in this instance is 0, which ensures that the loop starts with the second element; leaving the new element in place at the 0th index of the array.index
- In all other cases, the array will do the same thing as case 3 above, except the loop doesnāt start at index 0. In this case, the loop starts at the given value ofĀ
. A visualization of this can be seen earlier in this article; under theĀ Fixed Array OperationsĀ section.index
// Insert at index of array
public mutating func insert(element: T, at index: Int) {
assert(index >= 0)
assert(index < maxSize)
if index == 0 && count == 0 {
array.append(element)
count += 1
} else if count == maxSize && index == 0 || count == maxSize && index != 0 {
array[index] = element
} else if index == 0 && count != maxSize {
array.append(defaultValue)
count += 1
for i in (index+1..<count).reversed() {
array[i] = array[i - 1]
}
array[index] = element
} else {
array.append(defaultValue)
count += 1
for i in (index..<count).reversed() {
array[i] = array[i - 1]
}
array[index] = element
}
}
The Fixed Array now looks like this:
struct FixedArray<T> {
private var maxSize: Int
private var defaultValue: T
private var array = [T]()
private var count = 0
public var elements: [T] {
return array
}
public var length: Int {
return count
}
public init(maxSize: Int, defaultValue: T) {
self.maxSize = maxSize
self.defaultValue = defaultValue
array = .init()
}
// Append to end of array
public mutating func appendToEnd(element: T) {
assert(count < maxSize)
array.append(element)
count += 1
}
// Insert at index of array
public mutating func insert(element: T, at index: Int) {
assert(index >= 0)
assert(index < maxSize)
if index == 0 && count == 0 {
array.append(element)
count += 1
} else if count == maxSize && index == 0 || count == maxSize && index != 0 {
array[index] = element
} else if index == 0 && count != maxSize {
array.append(defaultValue)
count += 1
for i in (index+1..<count).reversed() {
array[i] = array[i - 1]
}
array[index] = element
} else {
array.append(defaultValue)
count += 1
for i in (index..<count).reversed() {
array[i] = array[i - 1]
}
array[index] = element
}
}
}
To start adding elements to the array, use either theĀ
appendToEnd
Ā or insert
methods. Here, the integersĀ 1,Ā 2,Ā andĀ 3Ā are added to the end of the array. Then,Ā 5Ā is inserted at the 3rd index,Ā 6Ā is inserted at the 4th index, andĀ 4Ā is inserted at the 3rd index. The array ends upholdingĀ [1, 2, 3, 4, 5, 6]
.array.appendToEnd(element: 1)
array.appendToEnd(element: 2)
array.appendToEnd(element: 3)
array.elements
// [1, 2, 3]
array.insert(element: 5, at: 3)
array.elements
// [1, 2, 3, 5]
array.insert(element: 6, at: 4)
array.elements
// [1, 2, 3, 5, 6]
array.insert(element: 4, at: 3)
array.elements
// [1, 2, 3, 4, 5, 6]
Now that the array can have elements added to it, there needs to be a way to remove these elements. There are three ways to remove items in a fixed array:
- Remove the element at the end of the array.
- Remove the item based on a given index.
- Remove all of the items in the array.
To delete an item from the end, simply make sure thatĀ
count
Ā is greater than 0, use theĀ removeLast
Ā method on the array and decrement 1 from count
.// Delete from end of array
public mutating func deleteFromEnd() {
assert(count > 0)
array.removeLast()
count -= 1
}
Removing an element based on a given index is a bit trickier. TheĀ
index
must be less than or equal to 0, and it must also be less than the value of count
. After these assertions pass,Ā count
Ā is decremented by 1, the element at the given index is obtained inĀ result
Ā and theĀ remove
function of the Swift array is called with index as its parameter. Finally, the element that is stored inĀ result
Ā is returned.// Delete at index of array
public mutating func removeAt(index: Int) -> T {
assert(index >= 0)
assert(index < count)
count -= 1
let result = array[index]
array.remove(at: index)
return result
}
Below is the usage of these two methods. TheĀ
deleteFromEnd
Ā method does not return the element that is removed, but theĀ removeAt
Ā method does. How they are used is up to the programmer.array.deleteFromEnd()
array.elements
// [1, 2, 3, 4, 5]
array.removeAt(index: 1)
// Returns 2
array.elements
// [1, 3, 4, 5]
The final ādeleteā method is one that removes all of the elements from the array. The method loops through the value ofĀ
count
, removes the last element and decrementsĀ count
Ā by 1. Once it processes the array, it then setsĀ count
Ā to 0.// Remove all elements
public mutating func removeAll() {
for _ in 0..<count {
array.removeLast()
count -= 1
}
count = 0
}
Again, the usage of this method:
array.removeAll()
array.elements
// []
The methods seen here can be changed to whatever use-case the programmer may have, but they perform the basic actions that a Fixed Array should be able to perform.
One thing that needs to be seen is the usage of the subscripts. It was mentioned earlier but hasnāt been used in code yet.
Custom Subscript in a Fixed Array
Custom subscripts work the same way as regular subscripts. The difference is that you define what is returned when using the subscript. When an element needs to be accessed in a Fixed Array, (or other custom data type), itās accessed by using brackets āĀ
array[element]
.When defining a customĀ subscript in Swift, a return type must be defined. This is expected, as subscripts return elements. Here is the subscript again:
subscript(index: Int) -> T {
assert(index >= 0)
assert(index < count)
return array[index]
}
Imagine thatĀ
array
Ā holdsĀ [1, 2, 3, 4, 5, 6]
. Here is how an element would be accessed:print(array.element)
// [1, 2, 3, 4, 5, 6]
let firstElement = array[0]
let fourthElement = array[3]
print(firstElement)
// 1
print(fourthElement)
// 4
The Complete Fixed Array in Swift
That is it for the Fixed Array. This data structure can be implemented in any language, and the use case is up to the programmer. A Fixed Array can be used for situations such as tracking the number of enemies on screen in a game, creating a representation of a byte (bytes are a storage unit in computingĀ and are made up of 8 bits. Bits are the smallest unit used for storage on a computer), or limiting the number of family members that a user can add to their family account of a subscription.
Below is the complete Fixed Array in Swift, using aĀ generic type:
struct FixedArray<T> {
private var maxSize: Int
private var defaultValue: T
private var array = [T]()
private var count = 0
public var elements: [T] {
return array
}
public var length: Int {
return count
}
public init(maxSize: Int, defaultValue: T) {
self.maxSize = maxSize
self.defaultValue = defaultValue
array = .init()
}
// Append to end of array
public mutating func appendToEnd(element: T) {
assert(count < maxSize)
array.append(element)
count += 1
}
// Insert at index of array
public mutating func insert(element: T, at index: Int) {
assert(index >= 0)
assert(index < maxSize)
if index == 0 && count == 0 {
array.append(element)
count += 1
} else if count == maxSize && index == 0 || count == maxSize && index != 0 {
array[index] = element
} else if index == 0 && count != maxSize {
array.append(defaultValue)
count += 1
for i in (index+1..<count).reversed() {
array[i] = array[i - 1]
}
array[index] = element
} else {
array.append(defaultValue)
count += 1
for i in (index..<count).reversed() {
array[i] = array[i - 1]
}
array[index] = element
}
}
// Delete from end of array
public mutating func deleteFromEnd() {
assert(count > 0)
array.removeLast()
count -= 1
}
// Delete at index of array
public mutating func removeAt(index: Int) -> T {
assert(index >= 0)
assert(index < count)
count -= 1
let result = array[index]
array.remove(at: index)
return result
}
// Remove all elements
public mutating func removeAll() {
for _ in 0..<count {
array.removeLast()
count -= 1
}
count = 0
}
subscript(index: Int) -> T {
assert(index >= 0)
assert(index < count)
return array[index]
}
}
Conclusion
Thank you for reading. If youāre looking for more articles about computer science and software engineering, give these a read:
- Rotate Arrays in SwiftĀ by Steven Curtis
- CS Data Structures: 2D Array
- Computer Science: Recursion
- The Concept of Coupling
Also published on Medium's andrew-lundy