Skip to content

Commit

Permalink
Merge pull request #27 from pedropark99/multi-array
Browse files Browse the repository at this point in the history
Add section describing the use of `std.MultiArrayList`
  • Loading branch information
pedropark99 authored Aug 24, 2024
2 parents bde8c9c + f42758d commit f1e0c19
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 98 deletions.
113 changes: 112 additions & 1 deletion Chapters/09-data-structures.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ to talk about one of the key features of Zig in this chapter, which is `comptime
how we can use it to create generics in Zig.


## Dynamic Arrays
## Dynamic Arrays {#sec-dynamic-array}

In high level languages, arrays are usually dynamic. They easily grow
in size when they have to, and you don't need to worry about it.
Expand Down Expand Up @@ -834,7 +834,118 @@ a singly linked list or a doubly linked list, that might be very useful for you,



## Multi array structure

Zig introduces a new data structure called `MultiArrayList()`. It is a different version of the dynamic array
that we have introduced at @sec-dynamic-array. The difference between this structure and the `ArrayList()`
that we know from @sec-dynamic-array, is that `MultiArrayList()` creates a separate dynamic array
for each field of the struct that you provide as input.

Consider the following code example. We create a new custom struct called `Person`. This
struct contains three different data members, or, three different fields. As consequence,
when we provide this `Person` data type as input to `MultiArrayList()`, this
creates a "struct of three different arrays" called `PersonArray`. In other words,
this `PersonArray` is a struct that contains three internal dynamic arrays in it.
One array for each field found in the `Person` struct definition.


```{zig}
#| auto_main: false
const std = @import("std");
const Person = struct {
name: []const u8,
age: u8,
height: f32,
};
const PersonArray = std.MultiArrayList(Person);
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var people = PersonArray{};
defer people.deinit(allocator);
try people.append(allocator, .{
.name = "Auguste", .age = 15, .height = 1.54
});
try people.append(allocator, .{
.name = "Elena", .age = 26, .height = 1.65
});
try people.append(allocator, .{
.name = "Michael", .age = 64, .height = 1.87
});
}
```

In other words, instead of creating an array of "persons", the `MultiArrayList()` function
creates a "struct of arrays". Each data member of this struct is a different array that stores
the values of a specific field from the `Person` struct values that are added (or, appended) to the "struct of arrays".
One important detail is that each of these separate internal arrays stored inside `PersonArray`
are dynamic arrays. This means that these arrays can grow automatically as needed, to accomodate
more values.

The @fig-multi-array exposed below presents a diagram that describes the `PersonArray` struct
that we have created in the previous code example. Notice that the values of the data members
present in each of the three `Person` values that we have appended into the `PersonArray` object
that we have instantiated, are scattered across three different internal arrays of the `PersonArray` object.

![A diagram of the `PersonArray` struct.](./../Figures/multi-array.png){#fig-multi-array}

You can easily access each of these arrays separately, and iterate over the values of each array.
For that, you will need to call the `items()` method from the `PersonArray` object, and provide as input
to this method, the name of the field that you want to iterate over.
If you want to iterate through the `.age` array for example, then, you need to call `items(.age)` from
the `PersonArray` object, like in the example below:

```{zig}
#| eval: false
for (people.items(.age)) |*age| {
try stdout.print("Age: {d}\n", .{age.*});
}
```

```
Age: 15
Age: 26
Age: 64
```


In the above example, we are iterating over the values of the `.age` array, or,
the internal array of the `PersonArray` object that contains the values of the `age`
data member from the `Person` values that were added to the multi array struct.

In this example we are calling the `items()` method directly from the `PersonArray`
object. However, it is recommended on most situations to call this `items()` method
from a "slice object", which you can create from the `slice()` method.
The reason for this is that calling `items()` multiple times have better performance
if you use a slice object.

In other words, if you are planning to access only one of the
internal arrays from your "multi array struct", it is fine to call `items()` directly
from the multi array object. But if you need to access many of the internal arrays
from your "multi array struct", then, you will likely need to call `items()` more
than once, and, in such circustance, is better to call `items()` through a slice object.
The example below demonstrates the use of such object:

```{zig}
#| eval: false
var slice = people.slice();
for (slice.items(.age)) |*age| {
age.* += 10;
}
for (slice.items(.name), slice.items(.age)) |*n,*a| {
try stdout.print(
"Name: {s}, Age: {d}\n", .{n.*, a.*}
);
}
```

```
Name: Auguste, Age: 25
Name: Elena, Age: 36
Name: Michael, Age: 74
```


## Conclusion
Expand Down
Binary file added Figures/Powerpoint/multi-array.odp
Binary file not shown.
Binary file added Figures/multi-array.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions ZigExamples/data-structures/multi-array-list.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const Person = struct {
name: []const u8,
age: u8,
height: f32,
};
const PersonArray = std.MultiArrayList(Person);

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
var people = PersonArray{};
defer people.deinit(allocator);

try people.append(allocator, .{ .name = "Auguste", .age = 15, .height = 1.54 });
try people.append(allocator, .{ .name = "Elena", .age = 26, .height = 1.65 });
try people.append(allocator, .{ .name = "Michael", .age = 64, .height = 1.87 });

for (people.items(.age)) |*age| {
try stdout.print("Age: {d}\n", .{age.*});
}

var slice = people.slice();
for (slice.items(.age)) |*age| {
age.* += 10;
}
for (slice.items(.name), slice.items(.age)) |*n, *a| {
try stdout.print("Name: {s}, Age: {d}\n", .{ n.*, a.* });
}
}
8 changes: 5 additions & 3 deletions _freeze/Chapters/09-data-structures/execute-results/html.json

Large diffs are not rendered by default.

Loading

0 comments on commit f1e0c19

Please sign in to comment.