Skip to content

Commit bc4d667

Browse files
committed
add 4-binary
1 parent f0233c4 commit bc4d667

20 files changed

+784
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
function concat(arrays) {
2+
// sum of individual array lengths
3+
let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
4+
5+
if (!arrays.length) return null;
6+
7+
let result = new Uint8Array(totalLength);
8+
9+
// for each array - copy it over result
10+
// next array is copied right after the previous one
11+
let length = 0;
12+
for(let array of arrays) {
13+
result.set(array, length);
14+
length += array.length;
15+
}
16+
17+
return result;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function concat(arrays) {
2+
// ...your code...
3+
}
4+
5+
let chunks = [
6+
new Uint8Array([0, 1, 2]),
7+
new Uint8Array([3, 4, 5]),
8+
new Uint8Array([6, 7, 8])
9+
];
10+
11+
console.log(Array.from(concat(chunks))); // 0, 1, 2, 3, 4, 5, 6, 7, 8
12+
13+
console.log(concat(chunks).constructor.name); // Uint8Array
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
describe("concat", function() {
2+
let chunks = [
3+
new Uint8Array([0, 1, 2]),
4+
new Uint8Array([3, 4, 5]),
5+
new Uint8Array([6, 7, 8])
6+
];
7+
8+
it("result has the same array type", function() {
9+
10+
let result = concat(chunks);
11+
12+
assert.equal(result.constructor, Uint8Array);
13+
});
14+
15+
it("concatenates arrays", function() {
16+
17+
let result = concat(chunks);
18+
19+
assert.deepEqual(result, new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]));
20+
21+
});
22+
23+
it("returns empty array on empty input", function() {
24+
25+
let result = concat([]);
26+
27+
assert.equal(result.length, 0);
28+
29+
});
30+
31+
});

4-binary/01-arraybuffer-binary-arrays/01-concat/solution.md

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
# Concatenate typed arrays
3+
4+
Given an array of `Uint8Array`, write a function `concat(arrays)` that returns a concatenation of them into a single array.
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# ArrayBuffer, binary arrays
2+
3+
In web-development we meet binary data mostly while dealing with files (create, upload, download). Another typical use case is image processing.
4+
5+
That's all possible in JavaScript, and binary operations are high-performant.
6+
7+
Although, there's a bit of confusion, because there are many classes. To name a few:
8+
- `ArrayBuffer`, `Uint8Array`, `DataView`, `Blob`, `File`, etc.
9+
10+
Binary data in JavaScript is implemented in a non-standard way, compared to other languages. But when we sort things out, everything becomes fairly simple.
11+
12+
**The basic binary object is `ArrayBuffer` -- a reference to a fixed-length contiguous memory area.**
13+
14+
We create it like this:
15+
```js run
16+
let buffer = new ArrayBuffer(16); // create a buffer of length 16
17+
alert(buffer.byteLength); // 16
18+
```
19+
20+
This allocates a contiguous memory area of 16 bytes and pre-fills it with zeroes.
21+
22+
```warn header="`ArrayBuffer` is not an array of something"
23+
Let's eliminate a possible source of confusion. `ArrayBuffer` has nothing in common with `Array`:
24+
- It has a fixed length, we can't increase or decrease it.
25+
- It takes exactly that much space in the memory.
26+
- To access individual bytes, another "view" object is needed, not `buffer[index]`.
27+
```
28+
29+
`ArrayBuffer` is a memory area. What's stored in it? It has no clue. Just a raw sequence of bytes.
30+
31+
**To manipulate an `ArrayBuffer`, we need to use a "view" object.**
32+
33+
A view object does not store anything on it's own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`.
34+
35+
For instance:
36+
37+
- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer".
38+
- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer".
39+
- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer".
40+
- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from <code>5.0x10<sup>-324</sup></code> to <code>1.8x10<sup>308</sup></code>.
41+
42+
So, the binary data in an `ArrayBuffer` of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers (2 bytes each), or 4 even bigger (4 bytes each), or 2 floating-point values with high precision (8 bytes each).
43+
44+
![](arraybuffer-views.png)
45+
46+
`ArrayBuffer` is the core object, the root of everything, the raw binary data.
47+
48+
But if we're going to write into it, or iterate over it, basically for almost any operation – we must use a view, e.g:
49+
50+
```js run
51+
let buffer = new ArrayBuffer(16); // create a buffer of length 16
52+
53+
*!*
54+
let view = new Uint32Array(buffer); // treat buffer as a sequence of 32-bit integers
55+
56+
alert(Uint32Array.BYTES_PER_ELEMENT); // 4 bytes per integer
57+
*/!*
58+
59+
alert(view.length); // 4, it stores that many integers
60+
alert(view.byteLength); // 16, the size in bytes
61+
62+
// let's write a value
63+
view[0] = 123456;
64+
65+
// iterate over values
66+
for(let num of view) {
67+
alert(num); // 123456, then 0, 0, 0 (4 values total)
68+
}
69+
70+
```
71+
72+
## TypedArray
73+
74+
The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities.
75+
76+
They are much more like regular arrays: have indexes and iterable.
77+
78+
79+
A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types.
80+
81+
There are 5 variants of arguments:
82+
83+
```js
84+
new TypedArray(buffer, [byteOffset], [length]);
85+
new TypedArray(object);
86+
new TypedArray(typedArray);
87+
new TypedArray(length);
88+
new TypedArray();
89+
```
90+
91+
1. If an `ArrayBuffer` argument is supplied, the view is created over it. We used that syntax already.
92+
93+
Optionally we can provide `byteOffset` to start from (0 by default) and the `length` (till the end of the buffer by default), then the view will cover only a part of the `buffer`.
94+
95+
2. If an `Array`, or any array-like object is given, it creates a typed array of the same length and copies the content.
96+
97+
We can use it to pre-fill the array with the data:
98+
```js run
99+
*!*
100+
let arr = new Uint8Array([0, 1, 2, 3]);
101+
*/!*
102+
alert( arr.length ); // 4
103+
alert( arr[1] ); // 1
104+
```
105+
3. If another `TypedArray` is supplied, it does the same: creates a typed array of the same length and copies values. Values are converted to the new type in the process.
106+
```js run
107+
let arr16 = new Uint16Array([1, 1000]);
108+
*!*
109+
let arr8 = new Uint8Array(arr16);
110+
*/!*
111+
alert( arr8[0] ); // 1
112+
alert( arr8[1] ); // 232 (tried to copy 1000, but can't fit 1000 into 8 bits)
113+
```
114+
115+
4. For a numeric argument `length` -- creates the typed array to contain that many elements. Its byte length will be `length` multiplied by the number of bytes in a single item `TypedArray.BYTES_PER_ELEMENT`:
116+
```js run
117+
let arr = new Uint16Array(4); // create typed array for 4 integers
118+
alert( Uint16Array.BYTES_PER_ELEMENT ); // 2 bytes per integer
119+
alert( arr.byteLength ); // 8 (size in bytes)
120+
```
121+
122+
5. Without arguments, creates an zero-length typed array.
123+
124+
We can create a `TypedArray` directly, without mentioning `ArrayBuffer`. But a view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these cases except the first one (when provided).
125+
126+
To access the `ArrayBuffer`, there are properties:
127+
- `arr.buffer` -- references the `ArrayBuffer`.
128+
- `arr.byteLength` -- the length of the `ArrayBuffer`.
129+
130+
So, we can always move from one view to another:
131+
```js
132+
let arr8 = new Uint8Array([0, 1, 2, 3]);
133+
134+
// another view on the same data
135+
let arr16 = new Uint16Array(arr8.buffer);
136+
```
137+
138+
139+
Here's the list of typed arrays:
140+
141+
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
142+
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment (see below).
143+
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
144+
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
145+
146+
```warn header="No `int8` or similar single-valued types"
147+
Please note, despite of the names like `Int8Array`, there's no single-value type like `int`, or `int8` in JavaScript.
148+
149+
That's logical, as `Int8Array` is not an array of these individual values, but rather a view on `ArrayBuffer`.
150+
```
151+
152+
### Out-of-bounds behavior
153+
154+
What if we attempt to write an out-of-bounds value into a typed array? There will be no error. But extra bits are cut-off.
155+
156+
For instance, let's try to put 256 into `Uint8Array`. In binary form, 256 is `100000000` (9 bits), but `Uint8Array` only provides 8 bits per value, that makes the available range from 0 to 255.
157+
158+
For bigger numbers, only the rightmost (less significant) 8 bits are stored, and the rest is cut off:
159+
160+
![](8bit-integer-256.png)
161+
162+
So we'll get zero.
163+
164+
For 257, the binary form is `100000001` (9 bits), the rightmost 8 get stored, so we'll have `1` in the array:
165+
166+
![](8bit-integer-257.png)
167+
168+
In other words, the number modulo 2<sup>8</sup> is saved.
169+
170+
Here's the demo:
171+
172+
```js run
173+
let uint8array = new Uint8Array(16);
174+
175+
let num = 256;
176+
alert(num.toString(2)); // 100000000 (binary representation)
177+
178+
uint8array[0] = 256;
179+
uint8array[1] = 257;
180+
181+
alert(uint8array[0]); // 0
182+
alert(uint8array[1]); // 1
183+
```
184+
185+
`Uint8ClampedArray` is special in this aspect, its behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is useful for image processing.
186+
187+
## TypedArray methods
188+
189+
`TypedArray` has regular `Array` methods, with notable exceptions.
190+
191+
We can iterate, `map`, `slice`, `find`, `reduce` etc.
192+
193+
There are few things we can't do though:
194+
195+
- No `splice` -- we can't "delete" a value, because typed arrays are views on a buffer, and these are fixed, contiguous areas of memory. All we can do is to assign a zero.
196+
- No `concat` method.
197+
198+
There are two additional methods:
199+
200+
- `arr.set(fromArr, [offset])` copies all elements from `fromArr` to the `arr`, starting at position `offset` (0 by default).
201+
- `arr.subarray([begin, end])` creates a new view of the same type from `begin` to `end` (exclusive). That's similar to `slice` method (that's also supported), but doesn't copy anything -- just creates a new view, to operate on the given piece of data.
202+
203+
These methods allow us to copy typed arrays, mix them, create new arrays from existing ones, and so on.
204+
205+
206+
207+
## DataView
208+
209+
[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format.
210+
211+
- For typed arrays, the constructor dictates what the format is. The whole array is supposed to be uniform. The i-th number is `arr[i]`.
212+
- With `DataView` we access the data with methods like `.getUint8(i)` or `.getUint16(i)`. We choose the format at method call time instead of the construction time.
213+
214+
The syntax:
215+
216+
```js
217+
new DataView(buffer, [byteOffset], [byteLength])
218+
```
219+
220+
- **`buffer`** -- the underlying `ArrayBuffer`. Unlike typed arrays, `DataView` doesn't create a buffer on its own. We need to have it ready.
221+
- **`byteOffset`** -- the starting byte position of the view (by default 0).
222+
- **`byteLength`** -- the byte length of the view (by default till the end of `buffer`).
223+
224+
For instance, here we extract numbers in different formats from the same buffer:
225+
226+
```js run
227+
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
228+
229+
let dataView = new DataView(buffer);
230+
231+
// get 8-bit number at offset 0
232+
alert( dataView.getUint8(0) ); // 255
233+
234+
// now get 16-bit number at offset 0, that's 2 bytes, both with max value
235+
alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)
236+
237+
// get 32-bit number at offset 0
238+
alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
239+
240+
dataView.setUint32(0, 0); // set 4-byte number to zero
241+
```
242+
243+
`DataView` is great when we store mixed-format data in the same buffer. E.g we store a sequence of pairs (16-bit integer, 32-bit float). Then `DataView` allows to access them easily.
244+
245+
## Summary
246+
247+
`ArrayBuffer` is the core object, a reference to the fixed-length contiguous memory area.
248+
249+
To do almost any operation on `ArrayBuffer`, we need a view.
250+
251+
- It can be a `TypedArray`:
252+
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for unsigned integers of 8, 16, and 32 bits.
253+
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment.
254+
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
255+
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
256+
- Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`.
257+
258+
In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed.
259+
260+
There are also two additional terms:
261+
- `ArrayBufferView` is an umbrella term for all these kinds of views.
262+
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
263+
264+
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common terms, as it means "any kind of binary data" -- an `ArrayBuffer` or a view over it.
265+
266+
267+
Here's a cheatsheet:
268+
269+
![](arraybuffer-view-buffersource.png)

0 commit comments

Comments
 (0)