@zio.dev/zio-blocks 0.0.33 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
package/reference/chunk.md
CHANGED
|
@@ -3,574 +3,3799 @@ id: chunk
|
|
|
3
3
|
title: "Chunk"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`Chunk[A]` is an immutable, indexed sequence optimized for high-
|
|
6
|
+
`Chunk[A]` is an **immutable, indexed sequence** of elements of type `A`. Unlike `Array`, `Chunk` provides a purely functional interface with optimized performance for high-level operations. It is lazy on expensive operations like repeated concatenation (which use balanced tree structures) while remaining fast on access.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
`Chunk[A]`:
|
|
9
|
+
- Is purely functional and immutable
|
|
10
|
+
- Provides O(1) typical random access (O(log n) worst-case for tree-structured chunks)
|
|
11
|
+
- Optimizes concatenation using balanced tree structures (Conc-Trees)
|
|
12
|
+
- Automatically specializes primitive types for efficiency without boxing
|
|
13
|
+
- Lazily materializes only when necessary to maintain performance
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
Here is the type signature:
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
```scala
|
|
18
|
+
sealed abstract class Chunk[+A]
|
|
19
|
+
extends IndexedSeq[A]
|
|
20
|
+
with IndexedSeqOps[A, Chunk, Chunk[A]]
|
|
21
|
+
with StrictOptimizedSeqOps[A, Chunk, Chunk[A]]
|
|
22
|
+
with IterableFactoryDefaults[A, Chunk]
|
|
23
|
+
with Serializable
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Overview
|
|
27
|
+
|
|
28
|
+
`Chunk` represents a chunk of values. The implementation is backed by arrays for small chunks but transitions to lazy concatenation trees (`Chunk.Concat`) when building large chunks through repeated concatenation. This design eliminates the O(n²) behavior of naive list concatenation while remaining efficient for both element access and transformation.
|
|
29
|
+
|
|
30
|
+
`Chunk` has four main internal representations:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
┌─────────────────────────────┐
|
|
34
|
+
│ Chunk[A] │
|
|
35
|
+
└─────────────────────────────┘
|
|
36
|
+
△
|
|
37
|
+
│
|
|
38
|
+
┌──────────────┬──────┴────────┬──────────────┐
|
|
39
|
+
│ │ │ │
|
|
40
|
+
┌────▼───┐ ┌──────▼──────┐ ┌────▼──────┐ ┌───▼────┐
|
|
41
|
+
│ Empty │ │ Singleton │ │Array- │ │ Concat │
|
|
42
|
+
│ │ │(one element)│ │Backed │ │(tree) │
|
|
43
|
+
└────────┘ └─────────────┘ └───────────┘ └────────┘
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Chunks automatically choose the most efficient representation:
|
|
47
|
+
- **Empty**: singleton instance for zero elements
|
|
48
|
+
- **Singleton**: single element, no array allocation
|
|
49
|
+
- **Array-backed**: standard array for small sequences
|
|
50
|
+
- **Concat tree**: balanced binary tree for composite chunks, enabling O(log n) concatenation depth
|
|
51
|
+
|
|
52
|
+
The implementation is based on [Conc-Trees for Functional and Parallel Programming](http://aleksandar-prokopec.com/resources/docs/lcpc-conc-trees.pdf) by Aleksandar Prokopec and Martin Odersky.
|
|
53
|
+
|
|
54
|
+
## The Problem
|
|
55
|
+
|
|
56
|
+
When you work with sequences of data in functional programming, you often need to do two things that seem simple on the surface but are surprisingly tricky to do efficiently at the same time: you need to access elements quickly and merge sequences together without wasting time and memory.
|
|
57
|
+
|
|
58
|
+
Consider a practical scenario. You're building a data processing pipeline where you collect results from multiple parallel operations. Each operation produces a sequence of values, and you need to combine all of them into one final sequence. With a traditional array, combining sequences requires allocating a new, larger array and copying every single element from the source arrays into it. That's O(n) work just for the merging step. If you're doing this repeatedly—merging results from 10 operations, then 20, then 100—the copying overhead adds up quickly and becomes the bottleneck in your program.
|
|
59
|
+
|
|
60
|
+
On the other hand, if you use a linked list for efficiency with concatenation, you face a different problem: accessing the millionth element requires traversing through all the previous elements one by one. That's O(n) time for a single lookup. This works fine if you rarely need random access, but in real applications where you're searching, filtering, and transforming sequences, random access happens constantly. It's a painful tradeoff.
|
|
61
|
+
|
|
62
|
+
There's another subtle but important issue: memory overhead. A linked list that holds a million integers wastes significant memory because each node must store a pointer to the next node in addition to the actual value. An array is compact and efficient, but when you concatenate arrays, you're wasting both time (on copying) and memory (on allocating temporary intermediate arrays).
|
|
63
|
+
|
|
64
|
+
The real problem isn't just performance in isolation—it's that conventional sequence types force you to choose between mutually incompatible goals. You can optimize for random access (arrays) or for concatenation (lists), but not both efficiently. In a functional programming world where immutability is a core principle, this creates a tension: we want pure, immutable sequences that behave well in parallel processing scenarios where splitting and merging are fundamental operations. Yet the standard approaches either copy too much data or traverse too slowly.
|
|
20
65
|
|
|
21
|
-
|
|
66
|
+
This tension becomes especially acute when you're building systems that process large streams of data, perform data-parallel operations across multiple cores, or need to concatenate sequences as part of their normal operation. Every time you reach for an array and concatenate them, you're paying a hidden tax in copying. Every time you reach for a list, you're paying a hidden tax in traversal. Neither option feels quite right for a modern, functional programming experience.
|
|
22
67
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
68
|
+
## The Solution
|
|
69
|
+
|
|
70
|
+
Chunk solves this problem by combining the strengths of both arrays and balanced trees into a single, cohesive data structure. The key insight is to use different internal representations for different purposes: arrays for small, simple sequences (where the overhead of tree structures would hurt more than help), and balanced tree structures (specifically, Conc-Trees) for composite sequences built through concatenation and transformation.
|
|
71
|
+
|
|
72
|
+
When you create a small Chunk directly—say, `Chunk(1, 2, 3, 4, 5)`—it's backed by a simple array internally. You get all the benefits: O(1) random access, compact memory, cache-friendly performance. There's no overhead from tree structures or pointers. You're just using an array, exactly as you would expect.
|
|
73
|
+
|
|
74
|
+
But here's where it gets clever. When you concatenate two Chunks, instead of eagerly copying all the data into a new array, Chunk creates a lightweight tree node that simply links the two chunks together. This is nearly free—just a few pointer assignments. If you concatenate again, you're building a small balanced tree. The beauty is that this tree structure guarantees that no matter how many times you concatenate, random access still works in O(log n) time. The tree stays balanced through careful structural invariants, so the depth never grows too much. For practical sequence sizes you'd work with, O(log n) access is nearly as fast as O(1), with much better memory usage.
|
|
75
|
+
|
|
76
|
+
Chunk also includes specialized support for incremental building through the ChunkBuilder API. When you're constructing a sequence by repeatedly appending elements, ChunkBuilder uses a buffering strategy: it accumulates elements in a small array, and only when the array fills up does it append it to the growing Chunk tree. This means appending is effectively O(1) amortized time—comparable to dynamically sized arrays, but without the re-copying overhead when the array grows.
|
|
77
|
+
|
|
78
|
+
There's another important optimization: Chunk is designed to handle primitive types like `Int`, `Long`, `Double`, and `Byte` without boxing them. This is a significant performance win in practice. The specialized constructors and accessors ensure that a `Chunk[Int]` really does store integers directly in memory, not wrapped in Java Integer objects.
|
|
79
|
+
|
|
80
|
+
For functional programming specifically, Chunk embraces immutability completely. When you transform a Chunk, you get a new Chunk. The original remains unchanged. This makes Chunk naturally safe to share across parallel operations without locks or coordination. Different threads can safely read from the same Chunk simultaneously, and transformations produce new, independent Chunks.
|
|
81
|
+
|
|
82
|
+
The practical result is that Chunk gives you a sequence type that's genuinely efficient across all the operations you actually need: random access is fast, concatenation is fast, transformation is efficient, and memory usage stays reasonable. There's no invisible copying happening in the background, and no painful linear-time traversals. You get array-like performance where it matters most, without sacrificing the ability to build and merge sequences efficiently. For functional programmers and anyone working with immutable data structures, Chunk eliminates the artificial tradeoff between performance and purity.
|
|
28
83
|
|
|
29
84
|
## Installation
|
|
30
85
|
|
|
31
|
-
|
|
86
|
+
Chunk is available in the core `zio-blocks` library:
|
|
32
87
|
|
|
33
88
|
```scala
|
|
34
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "
|
|
89
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.51"
|
|
35
90
|
```
|
|
36
91
|
|
|
37
|
-
For
|
|
92
|
+
For Scala.js support:
|
|
38
93
|
|
|
39
94
|
```scala
|
|
40
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-chunk" % "
|
|
95
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-chunk" % "0.0.51"
|
|
41
96
|
```
|
|
42
97
|
|
|
43
|
-
|
|
98
|
+
Supports Scala 2.13.x and 3.x.
|
|
99
|
+
|
|
100
|
+
## Factory Methods
|
|
44
101
|
|
|
45
|
-
|
|
102
|
+
Chunk provides comprehensive factory methods for creating instances from various sources. Each factory is optimized for its data source with different performance and safety characteristics. Choose based on your data source and use case.
|
|
46
103
|
|
|
47
|
-
###
|
|
104
|
+
### Direct Construction
|
|
105
|
+
|
|
106
|
+
The simplest ways to create chunks from individual elements or single values:
|
|
107
|
+
|
|
108
|
+
#### `Chunk.apply` — Varargs Constructor
|
|
109
|
+
|
|
110
|
+
Create a chunk from individual elements using varargs syntax:
|
|
111
|
+
|
|
112
|
+
```scala
|
|
113
|
+
object Chunk {
|
|
114
|
+
def apply[A](as: A*): Chunk[A]
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The simplest way to create chunks from individual elements:
|
|
48
119
|
|
|
49
120
|
```scala
|
|
50
121
|
import zio.blocks.chunk.Chunk
|
|
51
122
|
|
|
52
123
|
val numbers = Chunk(1, 2, 3, 4, 5)
|
|
53
|
-
|
|
54
|
-
|
|
124
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
125
|
+
|
|
126
|
+
val strings = Chunk("alice", "bob", "charlie")
|
|
127
|
+
// strings: Chunk[String] = IndexedSeq("alice", "bob", "charlie")
|
|
128
|
+
|
|
129
|
+
val mixed = Chunk(1, "two", 3.0)
|
|
130
|
+
// mixed: Chunk[Int | String | Double] = IndexedSeq(1, "two", 3.0)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Performance:** O(n) — creates array-backed chunk, optimal for small to medium sizes.
|
|
134
|
+
|
|
135
|
+
#### `Chunk.single` — Single-Element Constructor
|
|
136
|
+
|
|
137
|
+
Create an efficient single-element chunk:
|
|
138
|
+
|
|
139
|
+
```scala
|
|
140
|
+
object Chunk {
|
|
141
|
+
def single[A](a: A): Chunk[A]
|
|
142
|
+
}
|
|
55
143
|
```
|
|
56
144
|
|
|
57
|
-
|
|
145
|
+
Convenient for wrapping individual values without array overhead:
|
|
58
146
|
|
|
59
147
|
```scala
|
|
60
148
|
import zio.blocks.chunk.Chunk
|
|
61
149
|
|
|
62
|
-
val
|
|
63
|
-
|
|
150
|
+
val one = Chunk.single("hello")
|
|
151
|
+
// one: Chunk[String] = IndexedSeq("hello")
|
|
152
|
+
|
|
153
|
+
val singleInt = Chunk.single(42)
|
|
154
|
+
// singleInt: Chunk[Int] = IndexedSeq(42)
|
|
64
155
|
```
|
|
65
156
|
|
|
66
|
-
|
|
157
|
+
**Performance:** O(1) — specialized for single-element chunks, no array allocation.
|
|
67
158
|
|
|
68
|
-
|
|
159
|
+
#### `Chunk.empty` — Empty Chunk
|
|
69
160
|
|
|
70
|
-
|
|
71
|
-
import zio.blocks.chunk.Chunk
|
|
161
|
+
Create an empty chunk (singleton instance):
|
|
72
162
|
|
|
73
|
-
|
|
74
|
-
|
|
163
|
+
```scala
|
|
164
|
+
object Chunk {
|
|
165
|
+
def empty[A]: Chunk[A]
|
|
166
|
+
}
|
|
75
167
|
```
|
|
76
168
|
|
|
77
|
-
|
|
169
|
+
Returns the shared empty chunk singleton:
|
|
78
170
|
|
|
79
171
|
```scala
|
|
80
172
|
import zio.blocks.chunk.Chunk
|
|
81
173
|
|
|
82
|
-
val
|
|
83
|
-
|
|
84
|
-
|
|
174
|
+
val empty = Chunk.empty[Int]
|
|
175
|
+
// empty: Chunk[Int] = IndexedSeq()
|
|
176
|
+
|
|
177
|
+
empty.length
|
|
178
|
+
// res3: Int = 0
|
|
179
|
+
|
|
180
|
+
empty.isEmpty
|
|
181
|
+
// res4: Boolean = true
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Performance:** O(1) — returns shared singleton, no allocation.
|
|
185
|
+
|
|
186
|
+
### Generation Methods
|
|
187
|
+
|
|
188
|
+
Create chunks by generating values using functions:
|
|
189
|
+
|
|
190
|
+
#### `Chunk.fill` — Repeat Element N Times
|
|
191
|
+
|
|
192
|
+
Create a chunk by repeating an element n times:
|
|
193
|
+
|
|
194
|
+
```scala
|
|
195
|
+
object Chunk {
|
|
196
|
+
def fill[A](n: Int)(elem: => A): Chunk[A]
|
|
197
|
+
}
|
|
85
198
|
```
|
|
86
199
|
|
|
87
|
-
|
|
200
|
+
Useful for creating uniform chunks:
|
|
88
201
|
|
|
89
202
|
```scala
|
|
90
203
|
import zio.blocks.chunk.Chunk
|
|
91
|
-
import java.util
|
|
92
204
|
|
|
93
|
-
val
|
|
94
|
-
|
|
95
|
-
javaList.add("two")
|
|
205
|
+
val repeated = Chunk.fill(5)("x")
|
|
206
|
+
// repeated: Chunk[String] = IndexedSeq("x", "x", "x", "x", "x")
|
|
96
207
|
|
|
97
|
-
val
|
|
208
|
+
val zeros = Chunk.fill(3)(0)
|
|
209
|
+
// zeros: Chunk[Int] = IndexedSeq(0, 0, 0)
|
|
210
|
+
|
|
211
|
+
val ones = Chunk.fill(4)(1)
|
|
212
|
+
// ones: Chunk[Int] = IndexedSeq(1, 1, 1, 1)
|
|
98
213
|
```
|
|
99
214
|
|
|
100
|
-
|
|
215
|
+
**Performance:** O(n) — creates array-backed chunk of size n.
|
|
216
|
+
|
|
217
|
+
#### `Chunk.iterate` — Repeatedly Apply Function
|
|
218
|
+
|
|
219
|
+
Create a chunk by repeatedly applying a function starting from an initial value:
|
|
220
|
+
|
|
221
|
+
```scala
|
|
222
|
+
object Chunk {
|
|
223
|
+
def iterate[A](start: A, len: Int)(f: A => A): Chunk[A]
|
|
224
|
+
}
|
|
225
|
+
```
|
|
101
226
|
|
|
102
|
-
|
|
227
|
+
Generates sequences by function iteration:
|
|
103
228
|
|
|
104
229
|
```scala
|
|
105
230
|
import zio.blocks.chunk.Chunk
|
|
106
|
-
import java.nio.ByteBuffer
|
|
107
231
|
|
|
108
|
-
val
|
|
109
|
-
|
|
232
|
+
val powers = Chunk.iterate(1, 5)(_ * 2)
|
|
233
|
+
// powers: Chunk[Int] = IndexedSeq(1, 2, 4, 8, 16)
|
|
234
|
+
|
|
235
|
+
val alphabet = Chunk.iterate('a', 3)(c => (c + 1).toChar)
|
|
236
|
+
// alphabet: Chunk[Char] = IndexedSeq('a', 'b', 'c')
|
|
237
|
+
|
|
238
|
+
val decreasing = Chunk.iterate(10, 4)(_ - 1)
|
|
239
|
+
// decreasing: Chunk[Int] = IndexedSeq(10, 9, 8, 7)
|
|
110
240
|
```
|
|
111
241
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
242
|
+
**Performance:** O(n) — function applied n times sequentially.
|
|
243
|
+
|
|
244
|
+
#### `Chunk.unfold` — Generate from State
|
|
245
|
+
|
|
246
|
+
Generate a chunk by repeatedly applying a function that returns an optional value:
|
|
247
|
+
|
|
248
|
+
```scala
|
|
249
|
+
object Chunk {
|
|
250
|
+
def unfold[S, A](s: S)(f: S => Option[(A, S)]): Chunk[A]
|
|
251
|
+
}
|
|
252
|
+
```
|
|
120
253
|
|
|
121
|
-
|
|
254
|
+
Powerful for generating chunks from state transitions:
|
|
122
255
|
|
|
123
256
|
```scala
|
|
124
257
|
import zio.blocks.chunk.Chunk
|
|
125
258
|
|
|
126
|
-
|
|
127
|
-
val
|
|
128
|
-
|
|
259
|
+
// Count from 1 to 5
|
|
260
|
+
val counted = Chunk.unfold(1) { n =>
|
|
261
|
+
if (n <= 5) Some((n, n + 1)) else None
|
|
262
|
+
}
|
|
263
|
+
// counted: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
264
|
+
|
|
265
|
+
// Generate fibonacci-like sequence
|
|
266
|
+
val fibs = Chunk.unfold((1, 1)) { case (a, b) =>
|
|
267
|
+
if (a <= 50) Some((a, (b, a + b))) else None
|
|
268
|
+
}
|
|
269
|
+
// fibs: Chunk[Int] = IndexedSeq(1, 1, 2, 3, 5, 8, 13, 21, 34)
|
|
129
270
|
```
|
|
130
271
|
|
|
131
|
-
|
|
272
|
+
**Performance:** O(n) where n is number of generated elements.
|
|
132
273
|
|
|
133
|
-
###
|
|
274
|
+
### Collection Conversion Methods
|
|
275
|
+
|
|
276
|
+
Create chunks from existing Scala collections:
|
|
277
|
+
|
|
278
|
+
#### `Chunk.fromIterable` — From Scala Collections
|
|
279
|
+
|
|
280
|
+
Convert any Scala iterable into a chunk:
|
|
281
|
+
|
|
282
|
+
```scala
|
|
283
|
+
object Chunk {
|
|
284
|
+
def fromIterable[A](it: Iterable[A]): Chunk[A]
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Supports all Scala collection types (List, Vector, Set, Seq, etc.). For Chunk and Vector, data may be shared; for others, data is copied:
|
|
134
289
|
|
|
135
290
|
```scala
|
|
136
291
|
import zio.blocks.chunk.Chunk
|
|
137
292
|
|
|
138
|
-
val
|
|
293
|
+
val list = List(1, 2, 3)
|
|
294
|
+
// list: List[Int] = List(1, 2, 3)
|
|
295
|
+
val chunk = Chunk.fromIterable(list)
|
|
296
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
139
297
|
|
|
140
|
-
val
|
|
141
|
-
|
|
142
|
-
val
|
|
143
|
-
|
|
144
|
-
val len = chunk.length // 5
|
|
298
|
+
val vector = Vector("x", "y", "z")
|
|
299
|
+
// vector: Vector[String] = Vector("x", "y", "z")
|
|
300
|
+
val chunkFromVec = Chunk.fromIterable(vector)
|
|
301
|
+
// chunkFromVec: Chunk[String] = IndexedSeq("x", "y", "z")
|
|
145
302
|
|
|
146
|
-
val
|
|
147
|
-
|
|
303
|
+
val set = Set(10, 20, 30)
|
|
304
|
+
// set: Set[Int] = Set(10, 20, 30)
|
|
305
|
+
val chunkFromSet = Chunk.fromIterable(set)
|
|
306
|
+
// chunkFromSet: Chunk[Int] = IndexedSeq(10, 20, 30)
|
|
148
307
|
```
|
|
149
308
|
|
|
150
|
-
|
|
309
|
+
**Performance:** O(n) for most types; O(1) for Vector (data sharing).
|
|
310
|
+
|
|
311
|
+
#### `Chunk.from` — Generic from Iterable (Alias)
|
|
312
|
+
|
|
313
|
+
Shorter alias for `fromIterable`:
|
|
314
|
+
|
|
315
|
+
```scala
|
|
316
|
+
object Chunk {
|
|
317
|
+
def from[A](it: Iterable[A]): Chunk[A]
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Provides a concise name for generic iterable conversion:
|
|
151
322
|
|
|
152
323
|
```scala
|
|
153
324
|
import zio.blocks.chunk.Chunk
|
|
154
325
|
|
|
155
|
-
val
|
|
156
|
-
|
|
326
|
+
val list = List(1, 2, 3)
|
|
327
|
+
// list: List[Int] = List(1, 2, 3)
|
|
328
|
+
val chunk = Chunk.from(list)
|
|
329
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
330
|
+
```
|
|
157
331
|
|
|
158
|
-
|
|
159
|
-
val b: Byte = bytes.byte(0) // unboxed access
|
|
332
|
+
**Performance:** Same as `fromIterable`.
|
|
160
333
|
|
|
161
|
-
|
|
162
|
-
|
|
334
|
+
#### `Chunk.fromIterator` — From Iterator
|
|
335
|
+
|
|
336
|
+
Consume an iterator and collect all elements into a chunk:
|
|
337
|
+
|
|
338
|
+
```scala
|
|
339
|
+
object Chunk {
|
|
340
|
+
def fromIterator[A](iterator: Iterator[A]): Chunk[A]
|
|
341
|
+
}
|
|
163
342
|
```
|
|
164
343
|
|
|
165
|
-
|
|
344
|
+
Exhausts the iterator and builds a chunk:
|
|
166
345
|
|
|
167
346
|
```scala
|
|
168
347
|
import zio.blocks.chunk.Chunk
|
|
169
348
|
|
|
170
|
-
val
|
|
349
|
+
val iter = Iterator(5, 10, 15)
|
|
350
|
+
// iter: Iterator[Int] = empty iterator
|
|
351
|
+
val chunk = Chunk.fromIterator(iter)
|
|
352
|
+
// chunk: Chunk[Int] = IndexedSeq(5, 10, 15)
|
|
171
353
|
|
|
172
|
-
|
|
173
|
-
val
|
|
174
|
-
|
|
175
|
-
val collected = chunk.collect { case n if n % 2 == 0 => n * 10 } // Chunk(20, 40)
|
|
354
|
+
// Iterator is now exhausted
|
|
355
|
+
val isEmpty = iter.hasNext
|
|
356
|
+
// isEmpty: Boolean = false
|
|
176
357
|
```
|
|
177
358
|
|
|
178
|
-
|
|
359
|
+
**Performance:** O(n) where n is number of elements in iterator.
|
|
360
|
+
|
|
361
|
+
#### `Chunk.fromArray` — From Array (Zero-Copy)
|
|
362
|
+
|
|
363
|
+
Create a chunk backed by an array without copying:
|
|
364
|
+
|
|
365
|
+
```scala
|
|
366
|
+
object Chunk {
|
|
367
|
+
def fromArray[A](array: Array[A]): Chunk[A]
|
|
368
|
+
}
|
|
369
|
+
```
|
|
179
370
|
|
|
180
|
-
|
|
371
|
+
**Critical Warning:** This is a zero-copy operation that **holds a direct reference to the provided array**. The array **must not be mutated** after creating the chunk. Mutations become visible through the chunk:
|
|
181
372
|
|
|
182
373
|
```scala
|
|
183
374
|
import zio.blocks.chunk.Chunk
|
|
184
375
|
|
|
185
|
-
val
|
|
186
|
-
|
|
376
|
+
val arr = Array(10, 20, 30)
|
|
377
|
+
// arr: Array[Int] = Array(10, 20, 30)
|
|
378
|
+
val chunk = Chunk.fromArray(arr)
|
|
379
|
+
// chunk: Chunk[Int] = IndexedSeq(10, 20, 30)
|
|
187
380
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
val prepended = 0 +: a // Chunk(0, 1, 2, 3)
|
|
381
|
+
chunk
|
|
382
|
+
// res12: Chunk[Int] = IndexedSeq(10, 20, 30)
|
|
191
383
|
```
|
|
192
384
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
Slicing operations create views and don't copy data:
|
|
385
|
+
Mutating the source array breaks immutability:
|
|
196
386
|
|
|
197
387
|
```scala
|
|
198
388
|
import zio.blocks.chunk.Chunk
|
|
199
389
|
|
|
200
|
-
val
|
|
390
|
+
val arr = Array(10, 20, 30)
|
|
391
|
+
val chunk = Chunk.fromArray(arr)
|
|
392
|
+
arr(0) = 99 // DANGER: mutation visible through chunk!
|
|
393
|
+
```
|
|
201
394
|
|
|
202
|
-
|
|
203
|
-
val lastThree = chunk.takeRight(3) // Chunk(6, 7, 8)
|
|
204
|
-
val dropped = chunk.drop(2) // Chunk(3, 4, 5, 6, 7, 8)
|
|
205
|
-
val sliced = chunk.slice(2, 5) // Chunk(3, 4, 5)
|
|
395
|
+
The mutation is visible:
|
|
206
396
|
|
|
207
|
-
|
|
397
|
+
```scala
|
|
398
|
+
chunk
|
|
399
|
+
// res15: Chunk[Int] = IndexedSeq(99, 20, 30)
|
|
208
400
|
```
|
|
209
401
|
|
|
210
|
-
|
|
402
|
+
**Safe Alternative:** Use `fromIterable` which copies data and guarantees immutability:
|
|
211
403
|
|
|
212
404
|
```scala
|
|
213
405
|
import zio.blocks.chunk.Chunk
|
|
214
406
|
|
|
215
|
-
val
|
|
407
|
+
val arr = Array(10, 20, 30)
|
|
408
|
+
// arr: Array[Int] = Array(10, 20, 30)
|
|
409
|
+
val chunk = Chunk.fromIterable(arr.toList) // Safe copy
|
|
410
|
+
// chunk: Chunk[Int] = IndexedSeq(10, 20, 30)
|
|
216
411
|
|
|
217
|
-
|
|
218
|
-
val dropWhileSmall = chunk.dropWhile(_ < 4) // Chunk(4, 5, 6)
|
|
219
|
-
val takeUntilBig = chunk.takeWhile(_ <= 3) // Chunk(1, 2, 3)
|
|
220
|
-
val dropUntilBig = chunk.dropUntil(_ > 3) // Chunk(5, 6)
|
|
412
|
+
// Now mutations don't affect chunk
|
|
221
413
|
```
|
|
222
414
|
|
|
223
|
-
|
|
415
|
+
**Performance:** O(1) zero-copy; O(n) for safe copy alternative.
|
|
224
416
|
|
|
225
|
-
|
|
226
|
-
import zio.blocks.chunk.Chunk
|
|
417
|
+
### Java Interoperability Methods
|
|
227
418
|
|
|
228
|
-
|
|
419
|
+
Create chunks from Java collections and buffers:
|
|
229
420
|
|
|
230
|
-
|
|
231
|
-
val product = chunk.foldRight(1)(_ * _) // 120
|
|
232
|
-
val summed = chunk.reduce(_ + _) // 15
|
|
421
|
+
#### From `java.nio` Buffers
|
|
233
422
|
|
|
234
|
-
|
|
423
|
+
Create chunks directly from Java NIO buffers (`ByteBuffer`, `CharBuffer`, etc.):
|
|
424
|
+
|
|
425
|
+
```scala
|
|
426
|
+
object Chunk {
|
|
427
|
+
def fromByteBuffer(buffer: ByteBuffer): Chunk[Byte]
|
|
428
|
+
def fromCharBuffer(buffer: CharBuffer): Chunk[Char]
|
|
429
|
+
def fromIntBuffer(buffer: IntBuffer): Chunk[Int]
|
|
430
|
+
def fromLongBuffer(buffer: LongBuffer): Chunk[Long]
|
|
431
|
+
def fromFloatBuffer(buffer: FloatBuffer): Chunk[Float]
|
|
432
|
+
def fromDoubleBuffer(buffer: DoubleBuffer): Chunk[Double]
|
|
433
|
+
def fromShortBuffer(buffer: ShortBuffer): Chunk[Short]
|
|
434
|
+
}
|
|
235
435
|
```
|
|
236
436
|
|
|
237
|
-
|
|
437
|
+
Working with NIO buffers is seamless:
|
|
238
438
|
|
|
239
439
|
```scala
|
|
240
440
|
import zio.blocks.chunk.Chunk
|
|
441
|
+
import java.nio.ByteBuffer
|
|
241
442
|
|
|
242
|
-
val
|
|
443
|
+
val buffer = ByteBuffer.wrap(Array[Byte](1, 2, 3))
|
|
444
|
+
// buffer: ByteBuffer = java.nio.HeapByteBuffer[pos=0 lim=3 cap=3]
|
|
445
|
+
val chunk = Chunk.fromByteBuffer(buffer)
|
|
446
|
+
// chunk: Chunk[Byte] = IndexedSeq(1, 2, 3)
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**Performance:** O(n) — copies data from buffer into array-backed chunk.
|
|
450
|
+
|
|
451
|
+
#### `Chunk.fromJavaIterable` — From Java Iterable
|
|
452
|
+
|
|
453
|
+
Create a chunk from a Java `Iterable`:
|
|
243
454
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
455
|
+
```scala
|
|
456
|
+
object Chunk {
|
|
457
|
+
def fromJavaIterable[A](iterable: java.lang.Iterable[A]): Chunk[A]
|
|
458
|
+
}
|
|
248
459
|
```
|
|
249
460
|
|
|
250
|
-
|
|
461
|
+
Interoperate with Java APIs that produce iterables:
|
|
251
462
|
|
|
252
463
|
```scala
|
|
253
464
|
import zio.blocks.chunk.Chunk
|
|
465
|
+
import java.util.Arrays
|
|
254
466
|
|
|
255
|
-
val
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
val withIndex = as.zipWithIndex // Chunk(("a",0), ("b",1), ("c",2))
|
|
260
|
-
val zipWith = as.zipWith(bs)(_ + _) // Chunk("a1", "b2", "c3")
|
|
261
|
-
val zipAll = as.zipAll(Chunk(1, 2)) // handles different lengths
|
|
467
|
+
val javaList = Arrays.asList("a", "b", "c")
|
|
468
|
+
// javaList: List[String] = [a, b, c]
|
|
469
|
+
val chunk = Chunk.fromJavaIterable(javaList)
|
|
470
|
+
// chunk: Chunk[String] = IndexedSeq("a", "b", "c")
|
|
262
471
|
```
|
|
263
472
|
|
|
264
|
-
|
|
473
|
+
**Performance:** O(n) — iterates and copies elements.
|
|
474
|
+
|
|
475
|
+
#### `Chunk.fromJavaIterator` — From Java Iterator
|
|
265
476
|
|
|
266
|
-
|
|
477
|
+
Consume a Java `Iterator` and collect its elements into a chunk:
|
|
478
|
+
|
|
479
|
+
```scala
|
|
480
|
+
object Chunk {
|
|
481
|
+
def fromJavaIterator[A](iterator: java.util.Iterator[A]): Chunk[A]
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Building a chunk from a Java iterator:
|
|
267
486
|
|
|
268
487
|
```scala
|
|
269
488
|
import zio.blocks.chunk.Chunk
|
|
489
|
+
import java.util.Arrays
|
|
490
|
+
|
|
491
|
+
val javaIter = Arrays.asList(1, 2, 3).iterator()
|
|
492
|
+
// javaIter: Iterator[Int] = java.util.Arrays$ArrayItr@29c50fed
|
|
493
|
+
val chunk = Chunk.fromJavaIterator(javaIter)
|
|
494
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Performance:** O(n) — exhausts iterator and builds chunk.
|
|
498
|
+
|
|
499
|
+
### Builder Methods
|
|
270
500
|
|
|
271
|
-
|
|
272
|
-
|
|
501
|
+
`ChunkBuilder[A]` is a mutable builder that accumulates elements incrementally and returns a `Chunk[A]` when complete. Use it when building chunks from elements that arrive one at a time or from streaming sources.
|
|
502
|
+
|
|
503
|
+
#### `Chunk.newBuilder` — Get a Builder
|
|
504
|
+
|
|
505
|
+
Obtain a fresh `ChunkBuilder` for incremental construction:
|
|
506
|
+
|
|
507
|
+
```scala
|
|
508
|
+
object Chunk {
|
|
509
|
+
def newBuilder[A]: ChunkBuilder[A]
|
|
510
|
+
}
|
|
273
511
|
```
|
|
274
512
|
|
|
275
|
-
|
|
513
|
+
A builder integrates with Scala's collection factory pattern:
|
|
276
514
|
|
|
277
515
|
```scala
|
|
278
|
-
import zio.blocks.chunk.Chunk
|
|
516
|
+
import zio.blocks.chunk.{Chunk, ChunkBuilder}
|
|
517
|
+
|
|
518
|
+
val builder = Chunk.newBuilder[Int]
|
|
519
|
+
// builder: ChunkBuilder[Int] = ChunkBuilder
|
|
520
|
+
builder.addOne(1)
|
|
521
|
+
// res21: ChunkBuilder[Int] = ChunkBuilder
|
|
522
|
+
builder.addOne(2)
|
|
523
|
+
// res22: ChunkBuilder[Int] = ChunkBuilder
|
|
524
|
+
val result = builder.result()
|
|
525
|
+
// result: Chunk[Int] = IndexedSeq(1, 2)
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Using ChunkBuilder for Incremental Construction
|
|
529
|
+
|
|
530
|
+
`ChunkBuilder[A]` is a mutable builder that accumulates elements and returns a `Chunk[A]`. Use it when building chunks from elements that arrive incrementally—over time, from streaming sources, or when the total size is unknown in advance.
|
|
279
531
|
|
|
280
|
-
|
|
281
|
-
val deduped = withDupes.dedupe // Chunk(1, 2, 3) - removes adjacent duplicates
|
|
532
|
+
When constructing a chunk from multiple sources, repeatedly calling `++` creates additional tree nodes and increases access depth (O(log n) per operation). Static construction methods like `Chunk.apply` or `Chunk.from` require all elements upfront. `ChunkBuilder` solves this by using an internal buffering strategy: elements accumulate in a small array, and only when full does the array append to the growing chunk tree. This yields O(1) amortized append cost—comparable to dynamically sized arrays, but without re-copying overhead. It is also the standard Scala mutable builder interface, so it integrates with `scala.collection` builders.
|
|
282
533
|
|
|
283
|
-
|
|
284
|
-
|
|
534
|
+
| Scenario | Right choice |
|
|
535
|
+
|--------------------------------------------------------------------------------------------|----------------------------------------------|
|
|
536
|
+
| Elements available all at once; constructing a static chunk | `Chunk.apply(...)` or `Chunk.from(iterable)` |
|
|
537
|
+
| Elements arrive incrementally; unknown size in advance; building from a stream or iterator | `ChunkBuilder` with `addOne` / `addAll` |
|
|
538
|
+
|
|
539
|
+
The signature of `ChunkBuilder` is:
|
|
540
|
+
|
|
541
|
+
```scala
|
|
542
|
+
object ChunkBuilder {
|
|
543
|
+
def make[A](): ChunkBuilder[A]
|
|
544
|
+
def make[A](capacityHint: Int): ChunkBuilder[A]
|
|
545
|
+
}
|
|
285
546
|
```
|
|
286
547
|
|
|
287
|
-
|
|
548
|
+
Here's a realistic example: aggregating results from a paginated API that returns chunks of records until a sentinel response:
|
|
288
549
|
|
|
289
550
|
```scala
|
|
290
|
-
import zio.blocks.chunk.Chunk
|
|
551
|
+
import zio.blocks.chunk.{Chunk, ChunkBuilder}
|
|
291
552
|
|
|
292
|
-
|
|
553
|
+
case class ApiResponse(records: List[String], hasMore: Boolean)
|
|
554
|
+
|
|
555
|
+
def fetchAllRecords(): Chunk[String] = {
|
|
556
|
+
@annotation.tailrec
|
|
557
|
+
def loop(response: ApiResponse, builder: ChunkBuilder[String]): Chunk[String] = {
|
|
558
|
+
val builderWithRecords = builder.addAll(response.records.iterator)
|
|
559
|
+
if (response.hasMore) {
|
|
560
|
+
// Simulate fetching next page
|
|
561
|
+
loop(ApiResponse(List("record3", "record4"), false), builderWithRecords)
|
|
562
|
+
} else {
|
|
563
|
+
builderWithRecords.result()
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
loop(ApiResponse(List("record1", "record2"), true), ChunkBuilder.make[String](1000))
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
In this scenario, the API may return 100 pages before completion. Using `Chunk.apply` would require buffering all responses in memory first. Using naive `++` concatenation repeatedly would create deep tree structures with O(log n) access overhead. `ChunkBuilder` handles pagination efficiently by maintaining a single O(1) amortized append mechanism throughout.
|
|
571
|
+
|
|
572
|
+
## Core Operations
|
|
573
|
+
|
|
574
|
+
Chunk provides a rich set of operations for accessing, transforming, and combining elements. Operations are organized by category:
|
|
575
|
+
|
|
576
|
+
### Element Access
|
|
577
|
+
|
|
578
|
+
Chunk provides several operations to inspect and retrieve individual elements efficiently. Whether you need random access by index, efficient access to boundaries, or to check the size, these methods offer fast, predictable performance:
|
|
579
|
+
|
|
580
|
+
#### `Chunk#apply` — Random Access
|
|
581
|
+
|
|
582
|
+
Access an element by index in O(log n) time (O(1) for array-backed chunks):
|
|
293
583
|
|
|
294
|
-
|
|
295
|
-
|
|
584
|
+
```scala
|
|
585
|
+
trait Chunk[+A] {
|
|
586
|
+
def apply(index: Int): A
|
|
587
|
+
}
|
|
296
588
|
```
|
|
297
589
|
|
|
298
|
-
|
|
590
|
+
Access elements by index in a chunk:
|
|
299
591
|
|
|
300
592
|
```scala
|
|
301
593
|
import zio.blocks.chunk.Chunk
|
|
302
|
-
import java.nio.charset.StandardCharsets
|
|
303
594
|
|
|
304
|
-
val
|
|
305
|
-
|
|
595
|
+
val chunk = Chunk(10, 20, 30, 40, 50)
|
|
596
|
+
```
|
|
306
597
|
|
|
307
|
-
|
|
308
|
-
val str2 = chars.asString // "Hello"
|
|
598
|
+
Accessing by index returns individual elements:
|
|
309
599
|
|
|
310
|
-
|
|
311
|
-
|
|
600
|
+
```scala
|
|
601
|
+
chunk(0)
|
|
602
|
+
// res25: Int = 10
|
|
603
|
+
chunk(2)
|
|
604
|
+
// res26: Int = 30
|
|
605
|
+
chunk(4)
|
|
606
|
+
// res27: Int = 50
|
|
312
607
|
```
|
|
313
608
|
|
|
314
|
-
|
|
609
|
+
#### `Chunk#head` and `Chunk#last` — First and Last Elements
|
|
610
|
+
|
|
611
|
+
Access the first or last element:
|
|
612
|
+
|
|
613
|
+
```scala
|
|
614
|
+
trait Chunk[+A] {
|
|
615
|
+
def head: A
|
|
616
|
+
def last: A
|
|
617
|
+
}
|
|
618
|
+
```
|
|
315
619
|
|
|
316
|
-
|
|
620
|
+
Accessing the first or last element is efficient:
|
|
317
621
|
|
|
318
622
|
```scala
|
|
319
623
|
import zio.blocks.chunk.Chunk
|
|
320
624
|
|
|
321
|
-
val
|
|
322
|
-
val materialized = complex.materialize // backed by a single array
|
|
625
|
+
val chunk = Chunk("a", "b", "c", "d")
|
|
323
626
|
```
|
|
324
627
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
`NonEmptyChunk[A]` is a chunk guaranteed to contain at least one element. This enables safe use of operations like `head` and `reduce`:
|
|
628
|
+
Getting the first and last elements is immediate:
|
|
328
629
|
|
|
329
630
|
```scala
|
|
330
|
-
|
|
631
|
+
chunk.head
|
|
632
|
+
// res29: String = "a"
|
|
633
|
+
chunk.last
|
|
634
|
+
// res30: String = "d"
|
|
635
|
+
```
|
|
331
636
|
|
|
332
|
-
|
|
637
|
+
#### `Chunk#length` and `Chunk#size` — Chunk Size
|
|
333
638
|
|
|
334
|
-
|
|
335
|
-
val sum: Int = nec.reduce(_ + _) // always safe
|
|
639
|
+
Get the number of elements (O(1) complexity):
|
|
336
640
|
|
|
337
|
-
|
|
338
|
-
|
|
641
|
+
```scala
|
|
642
|
+
trait Chunk[+A] {
|
|
643
|
+
def length: Int
|
|
644
|
+
def size: Int
|
|
645
|
+
}
|
|
339
646
|
```
|
|
340
647
|
|
|
341
|
-
|
|
648
|
+
Getting the chunk size is an O(1) operation:
|
|
342
649
|
|
|
343
650
|
```scala
|
|
344
|
-
import zio.blocks.chunk.
|
|
651
|
+
import zio.blocks.chunk.Chunk
|
|
345
652
|
|
|
346
|
-
val
|
|
347
|
-
|
|
348
|
-
val fromCons = NonEmptyChunk.fromCons(::(1, List(2, 3)))
|
|
349
|
-
val fromIterable = NonEmptyChunk.fromIterable(1, List(2, 3))
|
|
653
|
+
val chunk = Chunk(1, 2, 3, 4, 5)
|
|
654
|
+
```
|
|
350
655
|
|
|
351
|
-
|
|
352
|
-
|
|
656
|
+
Both `length` and `size` return the element count:
|
|
657
|
+
|
|
658
|
+
```scala
|
|
659
|
+
chunk.length
|
|
660
|
+
// res32: Int = 5
|
|
661
|
+
chunk.size
|
|
662
|
+
// res33: Int = 5
|
|
353
663
|
```
|
|
354
664
|
|
|
355
|
-
|
|
665
|
+
#### `Chunk#headOption` and `Chunk#lastOption` — Safe First and Last
|
|
666
|
+
|
|
667
|
+
Like `head` and `last`, but return `Option` to safely handle empty chunks:
|
|
356
668
|
|
|
357
669
|
```scala
|
|
358
|
-
|
|
670
|
+
trait Chunk[+A] {
|
|
671
|
+
def headOption: Option[A]
|
|
672
|
+
def lastOption: Option[A]
|
|
673
|
+
}
|
|
674
|
+
```
|
|
359
675
|
|
|
360
|
-
|
|
361
|
-
val chunk: Chunk[Int] = nec.toChunk
|
|
676
|
+
Accessing the first or last element safely yields an `Option`:
|
|
362
677
|
|
|
363
|
-
|
|
364
|
-
|
|
678
|
+
```scala
|
|
679
|
+
import zio.blocks.chunk.Chunk
|
|
680
|
+
|
|
681
|
+
val chunk = Chunk("a", "b", "c")
|
|
682
|
+
val empty = Chunk.empty[Int]
|
|
365
683
|
```
|
|
366
684
|
|
|
367
|
-
|
|
685
|
+
`headOption` and `lastOption` provide safe access:
|
|
686
|
+
|
|
687
|
+
```scala
|
|
688
|
+
chunk.headOption
|
|
689
|
+
// res35: Option[String] = Some("a")
|
|
690
|
+
chunk.lastOption
|
|
691
|
+
// res36: Option[String] = Some("c")
|
|
692
|
+
empty.headOption
|
|
693
|
+
// res37: Option[Int] = None
|
|
694
|
+
empty.lastOption
|
|
695
|
+
// res38: Option[Int] = None
|
|
696
|
+
```
|
|
368
697
|
|
|
369
|
-
|
|
370
|
-
- `map`, `flatMap`, `flatten`
|
|
371
|
-
- `append`, `prepend`, `++`
|
|
372
|
-
- `zip`, `zipWith`, `zipWithIndex`
|
|
373
|
-
- `sorted`, `sortBy`, `reverse`
|
|
374
|
-
- `distinct`, `materialize`
|
|
698
|
+
#### `Chunk#indexWhere` — Find Index of Matching Element
|
|
375
699
|
|
|
376
|
-
|
|
377
|
-
- `filter`, `filterNot`
|
|
378
|
-
- `collect`
|
|
379
|
-
- `tail`, `init`
|
|
700
|
+
Find the index of the first element that matches a predicate:
|
|
380
701
|
|
|
381
|
-
|
|
702
|
+
```scala
|
|
703
|
+
trait Chunk[+A] {
|
|
704
|
+
def indexWhere(f: A => Boolean): Int
|
|
705
|
+
}
|
|
706
|
+
```
|
|
382
707
|
|
|
383
|
-
|
|
708
|
+
Searching for a matching element returns its index or -1 if not found:
|
|
384
709
|
|
|
385
710
|
```scala
|
|
386
|
-
import zio.blocks.chunk.
|
|
711
|
+
import zio.blocks.chunk.Chunk
|
|
387
712
|
|
|
388
|
-
val
|
|
389
|
-
builder.addOne(1)
|
|
390
|
-
builder.addOne(2)
|
|
391
|
-
builder.addAll(List(3, 4, 5))
|
|
392
|
-
val result: Chunk[Int] = builder.result() // Chunk(1, 2, 3, 4, 5)
|
|
713
|
+
val numbers = Chunk(10, 20, 30, 40)
|
|
393
714
|
```
|
|
394
715
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
For primitives, use specialized builders for best performance:
|
|
716
|
+
Finding the index of the first even number:
|
|
398
717
|
|
|
399
718
|
```scala
|
|
400
|
-
|
|
719
|
+
numbers.indexWhere(_ % 25 == 0)
|
|
720
|
+
// res40: Int = -1
|
|
721
|
+
numbers.indexWhere(_ % 7 == 0)
|
|
722
|
+
// res41: Int = -1
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
#### `Chunk#tail` and `Chunk#init` — Rest and Initial Segments
|
|
401
726
|
|
|
402
|
-
|
|
403
|
-
intBuilder.addOne(1)
|
|
404
|
-
intBuilder.addOne(2)
|
|
405
|
-
val ints: Chunk[Int] = intBuilder.result()
|
|
727
|
+
`tail` returns all elements except the first, `init` returns all elements except the last:
|
|
406
728
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
729
|
+
```scala
|
|
730
|
+
trait Chunk[+A] {
|
|
731
|
+
def tail: Chunk[A]
|
|
732
|
+
def init: Chunk[A]
|
|
733
|
+
}
|
|
411
734
|
```
|
|
412
735
|
|
|
413
|
-
|
|
736
|
+
Taking the rest of the chunk after the first element, or all but the last:
|
|
414
737
|
|
|
415
|
-
|
|
738
|
+
```scala
|
|
739
|
+
import zio.blocks.chunk.Chunk
|
|
416
740
|
|
|
417
|
-
|
|
741
|
+
val chunk = Chunk(1, 2, 3, 4)
|
|
742
|
+
```
|
|
418
743
|
|
|
419
|
-
|
|
744
|
+
`tail` drops the first element, `init` drops the last:
|
|
420
745
|
|
|
421
746
|
```scala
|
|
422
|
-
|
|
747
|
+
chunk.tail
|
|
748
|
+
// res43: Chunk[Int] = IndexedSeq(2, 3, 4)
|
|
749
|
+
chunk.init
|
|
750
|
+
// res44: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Transformations
|
|
423
754
|
|
|
424
|
-
|
|
425
|
-
val bits = bytes.asBitsByte // Chunk of 16 booleans
|
|
755
|
+
Chunk supports functional transformations that let you shape your data in powerful ways. Map applies a function to every element, flatMap chains operations and flattens results, filter keeps only elements that matter, and collect extracts values from nested structures:
|
|
426
756
|
|
|
427
|
-
|
|
428
|
-
val intBits = ints.asBitsInt(Chunk.BitChunk.Endianness.BigEndian)
|
|
757
|
+
#### `Chunk#map` — Transform Elements
|
|
429
758
|
|
|
430
|
-
|
|
431
|
-
|
|
759
|
+
Apply a function to each element:
|
|
760
|
+
|
|
761
|
+
```scala
|
|
762
|
+
trait Chunk[+A] {
|
|
763
|
+
def map[B](f: A => B): Chunk[B]
|
|
764
|
+
}
|
|
432
765
|
```
|
|
433
766
|
|
|
434
|
-
|
|
767
|
+
Mapping a function across elements creates a new chunk:
|
|
435
768
|
|
|
436
769
|
```scala
|
|
437
770
|
import zio.blocks.chunk.Chunk
|
|
438
771
|
|
|
439
|
-
val
|
|
440
|
-
|
|
772
|
+
val numbers = Chunk(1, 2, 3, 4)
|
|
773
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
774
|
+
val doubled = numbers.map(_ * 2)
|
|
775
|
+
// doubled: Chunk[Int] = IndexedSeq(2, 4, 6, 8)
|
|
776
|
+
|
|
777
|
+
val strings = Chunk("hello", "world")
|
|
778
|
+
// strings: Chunk[String] = IndexedSeq("hello", "world")
|
|
779
|
+
val lengths = strings.map(_.length)
|
|
780
|
+
// lengths: Chunk[Int] = IndexedSeq(5, 5)
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
#### `Chunk#flatMap` — Flat Transformation
|
|
784
|
+
|
|
785
|
+
Map each element to an iterable collection and flatten the result:
|
|
441
786
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
787
|
+
```scala
|
|
788
|
+
trait Chunk[+A] {
|
|
789
|
+
def flatMap[B](f: A => IterableOnce[B]): Chunk[B]
|
|
790
|
+
}
|
|
446
791
|
```
|
|
447
792
|
|
|
448
|
-
|
|
793
|
+
Flat-mapping chains transformations and flattens the result. The function can return any `IterableOnce` (such as `Chunk`, `List`, `Vector`, `Array`, etc.):
|
|
449
794
|
|
|
450
795
|
```scala
|
|
451
796
|
import zio.blocks.chunk.Chunk
|
|
452
797
|
|
|
453
|
-
val
|
|
454
|
-
|
|
798
|
+
val numbers = Chunk(1, 2, 3)
|
|
799
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
800
|
+
val expanded = numbers.flatMap(n => Chunk(n, n * 10))
|
|
801
|
+
// expanded: Chunk[Int] = IndexedSeq(1, 10, 2, 20, 3, 30)
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
#### `Chunk#filter` — Keep Matching Elements
|
|
805
|
+
|
|
806
|
+
Keep only elements that match a predicate:
|
|
455
807
|
|
|
456
|
-
|
|
457
|
-
|
|
808
|
+
```scala
|
|
809
|
+
trait Chunk[+A] {
|
|
810
|
+
def filter(f: A => Boolean): Chunk[A]
|
|
811
|
+
}
|
|
458
812
|
```
|
|
459
813
|
|
|
460
|
-
|
|
814
|
+
Filtering by a predicate keeps only matching elements:
|
|
461
815
|
|
|
462
816
|
```scala
|
|
463
817
|
import zio.blocks.chunk.Chunk
|
|
464
818
|
|
|
465
|
-
val
|
|
466
|
-
|
|
819
|
+
val numbers = Chunk(1, 2, 3, 4, 5, 6)
|
|
820
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
|
|
821
|
+
val evens = numbers.filter(_ % 2 == 0)
|
|
822
|
+
// evens: Chunk[Int] = IndexedSeq(2, 4, 6)
|
|
823
|
+
|
|
824
|
+
val longWords = Chunk("a", "hello", "b", "world")
|
|
825
|
+
// longWords: Chunk[String] = IndexedSeq("a", "hello", "b", "world")
|
|
826
|
+
val filtered = longWords.filter(_.length > 1)
|
|
827
|
+
// filtered: Chunk[String] = IndexedSeq("hello", "world")
|
|
467
828
|
```
|
|
468
829
|
|
|
469
|
-
|
|
830
|
+
#### `Chunk#collect` — Filter-Map Combined
|
|
470
831
|
|
|
471
|
-
|
|
832
|
+
Apply a partial function, keeping only successful matches:
|
|
472
833
|
|
|
473
834
|
```scala
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
val value = map.get("b") // Some(2)
|
|
479
|
-
val updated = map.updated("d", 4)
|
|
480
|
-
val removed = map.removed("b")
|
|
835
|
+
trait Chunk[+A] {
|
|
836
|
+
def collect[B](pf: PartialFunction[A, B]): Chunk[B]
|
|
837
|
+
}
|
|
481
838
|
```
|
|
482
839
|
|
|
483
|
-
|
|
840
|
+
Collecting combines filtering and mapping in one operation:
|
|
484
841
|
|
|
485
842
|
```scala
|
|
486
|
-
import zio.blocks.chunk.
|
|
843
|
+
import zio.blocks.chunk.Chunk
|
|
487
844
|
|
|
488
|
-
val
|
|
489
|
-
|
|
490
|
-
val
|
|
491
|
-
|
|
845
|
+
val values: Chunk[Any] = Chunk(1, "hello", 2, "world", 3)
|
|
846
|
+
// values: Chunk[Any] = IndexedSeq(1, "hello", 2, "world", 3)
|
|
847
|
+
val onlyInts = values.collect { case n: Int => n * 10 }
|
|
848
|
+
// onlyInts: Chunk[Int] = IndexedSeq(10, 20, 30)
|
|
492
849
|
```
|
|
493
850
|
|
|
494
|
-
|
|
851
|
+
#### `Chunk#sorted` — Sort Elements
|
|
495
852
|
|
|
496
|
-
|
|
853
|
+
Sort elements using an ordering:
|
|
497
854
|
|
|
498
855
|
```scala
|
|
499
|
-
|
|
856
|
+
trait Chunk[+A] {
|
|
857
|
+
def sorted[A1 >: A](implicit ord: Ordering[A1]): Chunk[A]
|
|
858
|
+
}
|
|
859
|
+
```
|
|
500
860
|
|
|
501
|
-
|
|
861
|
+
Sorting arranges elements in order:
|
|
862
|
+
|
|
863
|
+
```scala
|
|
864
|
+
import zio.blocks.chunk.Chunk
|
|
502
865
|
|
|
503
|
-
val
|
|
504
|
-
|
|
505
|
-
val
|
|
866
|
+
val unsorted = Chunk(3, 1, 4, 1, 5, 9, 2, 6)
|
|
867
|
+
// unsorted: Chunk[Int] = IndexedSeq(3, 1, 4, 1, 5, 9, 2, 6)
|
|
868
|
+
val sorted = unsorted.sorted
|
|
869
|
+
// sorted: Chunk[Int] = IndexedSeq(1, 1, 2, 3, 4, 5, 6, 9)
|
|
506
870
|
|
|
507
|
-
val
|
|
508
|
-
|
|
871
|
+
val strings = Chunk("zebra", "apple", "banana")
|
|
872
|
+
// strings: Chunk[String] = IndexedSeq("zebra", "apple", "banana")
|
|
873
|
+
val sortedStrings = strings.sorted
|
|
874
|
+
// sortedStrings: Chunk[String] = IndexedSeq("apple", "banana", "zebra")
|
|
509
875
|
```
|
|
510
876
|
|
|
511
|
-
|
|
877
|
+
#### `Chunk#sortBy` — Sort by Key
|
|
512
878
|
|
|
513
|
-
|
|
879
|
+
Sort elements according to a key function:
|
|
514
880
|
|
|
515
881
|
```scala
|
|
516
|
-
|
|
882
|
+
trait Chunk[+A] {
|
|
883
|
+
def sortBy[B](f: A => B)(implicit ord: Ordering[B]): Chunk[A]
|
|
884
|
+
}
|
|
885
|
+
```
|
|
517
886
|
|
|
518
|
-
|
|
519
|
-
val indexed = map.indexed // O(1) lookups, extra memory for index
|
|
887
|
+
Sorting by a key produces a new ordered chunk:
|
|
520
888
|
|
|
521
|
-
|
|
522
|
-
|
|
889
|
+
```scala
|
|
890
|
+
import zio.blocks.chunk.Chunk
|
|
523
891
|
|
|
524
|
-
|
|
892
|
+
case class Person(name: String, age: Int)
|
|
893
|
+
val people = Chunk(Person("Alice", 32), Person("Bob", 25), Person("Carol", 40))
|
|
894
|
+
// people: Chunk[Person] = IndexedSeq(
|
|
895
|
+
// Person(name = "Alice", age = 32),
|
|
896
|
+
// Person(name = "Bob", age = 25),
|
|
897
|
+
// Person(name = "Carol", age = 40)
|
|
898
|
+
// )
|
|
899
|
+
```
|
|
525
900
|
|
|
526
|
-
|
|
901
|
+
Sorting by age orders the people from youngest to oldest:
|
|
527
902
|
|
|
528
903
|
```scala
|
|
529
|
-
|
|
904
|
+
people.sortBy(_.age)
|
|
905
|
+
// res51: Chunk[Person] = IndexedSeq(
|
|
906
|
+
// Person(name = "Bob", age = 25),
|
|
907
|
+
// Person(name = "Alice", age = 32),
|
|
908
|
+
// Person(name = "Carol", age = 40)
|
|
909
|
+
// )
|
|
910
|
+
```
|
|
530
911
|
|
|
531
|
-
|
|
912
|
+
#### `Chunk#collectFirst` — Collect First Matching Partial Function
|
|
532
913
|
|
|
533
|
-
|
|
534
|
-
val vector: Vector[Int] = chunk.toVector
|
|
535
|
-
val array: Array[Int] = chunk.toArray
|
|
914
|
+
Apply a partial function to the first matching element and return the result as an option:
|
|
536
915
|
|
|
537
|
-
|
|
916
|
+
```scala
|
|
917
|
+
trait Chunk[+A] {
|
|
918
|
+
def collectFirst[B](pf: PartialFunction[A, B]): Option[B]
|
|
919
|
+
}
|
|
538
920
|
```
|
|
539
921
|
|
|
540
|
-
|
|
922
|
+
Collecting the first match returns an optional result:
|
|
541
923
|
|
|
542
924
|
```scala
|
|
543
925
|
import zio.blocks.chunk.Chunk
|
|
544
926
|
|
|
545
|
-
val
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
Chunk
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
927
|
+
val values = Chunk(1, "two", 3, "four")
|
|
928
|
+
```
|
|
929
|
+
|
|
930
|
+
Extract the first string if present:
|
|
931
|
+
|
|
932
|
+
```scala
|
|
933
|
+
values.collectFirst { case s: String => s }
|
|
934
|
+
// res53: Option[String] = Some("two")
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
#### `Chunk#filterNot` — Keep Non-Matching Elements
|
|
938
|
+
|
|
939
|
+
Keep only elements that do NOT match a predicate:
|
|
940
|
+
|
|
941
|
+
```scala
|
|
942
|
+
trait Chunk[+A] {
|
|
943
|
+
def filterNot(f: A => Boolean): Chunk[A]
|
|
944
|
+
}
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
Filtering with a negated predicate removes elements that satisfy the condition:
|
|
948
|
+
|
|
949
|
+
```scala
|
|
950
|
+
import zio.blocks.chunk.Chunk
|
|
951
|
+
|
|
952
|
+
val numbers = Chunk(1, 2, 3, 4, 5, 6)
|
|
953
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
Keep only odd numbers:
|
|
957
|
+
|
|
958
|
+
```scala
|
|
959
|
+
numbers.filterNot(_ % 2 == 0)
|
|
960
|
+
// res55: Chunk[Int] = IndexedSeq(1, 3, 5)
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
#### `Chunk#distinct` — Remove All Duplicates
|
|
964
|
+
|
|
965
|
+
Remove duplicate elements, keeping only one occurrence of each unique value:
|
|
966
|
+
|
|
967
|
+
```scala
|
|
968
|
+
trait Chunk[+A] {
|
|
969
|
+
def distinct: Chunk[A]
|
|
970
|
+
}
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
Distinct eliminates duplicates based on equality:
|
|
974
|
+
|
|
975
|
+
```scala
|
|
976
|
+
import zio.blocks.chunk.Chunk
|
|
977
|
+
|
|
978
|
+
val withDupes = Chunk(1, 2, 3, 2, 4, 1, 5)
|
|
979
|
+
```
|
|
980
|
+
|
|
981
|
+
Keeping only unique values:
|
|
982
|
+
|
|
983
|
+
```scala
|
|
984
|
+
withDupes.distinct
|
|
985
|
+
// res57: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
#### `Chunk#dedupe` — Remove Consecutive Duplicates
|
|
989
|
+
|
|
990
|
+
Remove only consecutive duplicate elements, preserving the first occurrence in each run:
|
|
991
|
+
|
|
992
|
+
```scala
|
|
993
|
+
trait Chunk[+A] {
|
|
994
|
+
def dedupe: Chunk[A]
|
|
995
|
+
}
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
Deduplication differs from `distinct` by only collapsing adjacent duplicates:
|
|
999
|
+
|
|
1000
|
+
```scala
|
|
1001
|
+
import zio.blocks.chunk.Chunk
|
|
1002
|
+
|
|
1003
|
+
val runs = Chunk(1, 1, 2, 2, 2, 3, 1, 1)
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
Removing consecutive repeats yields:
|
|
1007
|
+
|
|
1008
|
+
```scala
|
|
1009
|
+
runs.dedupe
|
|
1010
|
+
// res59: Chunk[Int] = IndexedSeq(1, 2, 3, 1)
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
#### `Chunk#flatten` — Flatten Nested Chunks
|
|
1014
|
+
|
|
1015
|
+
Flatten a chunk of chunks into a single chunk:
|
|
1016
|
+
|
|
1017
|
+
```scala
|
|
1018
|
+
trait Chunk[+A] {
|
|
1019
|
+
def flatten[B](implicit ev: A <:< Chunk[B]): Chunk[B]
|
|
1020
|
+
}
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
Flattening concatenates nested chunks:
|
|
1024
|
+
|
|
1025
|
+
```scala
|
|
1026
|
+
import zio.blocks.chunk.Chunk
|
|
1027
|
+
|
|
1028
|
+
val nested = Chunk(Chunk(1, 2), Chunk(3), Chunk(4, 5))
|
|
1029
|
+
// nested: Chunk[Chunk[Int]] = IndexedSeq(
|
|
1030
|
+
// IndexedSeq(1, 2),
|
|
1031
|
+
// IndexedSeq(3),
|
|
1032
|
+
// IndexedSeq(4, 5)
|
|
1033
|
+
// )
|
|
1034
|
+
```
|
|
1035
|
+
|
|
1036
|
+
A single flattened chunk results:
|
|
1037
|
+
|
|
1038
|
+
```scala
|
|
1039
|
+
nested.flatten
|
|
1040
|
+
// res61: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
#### `Chunk#mapAccum` — Stateful Map with Accumulator
|
|
1044
|
+
|
|
1045
|
+
Map over elements while accumulating state, returning the final state and the transformed chunk:
|
|
1046
|
+
|
|
1047
|
+
```scala
|
|
1048
|
+
trait Chunk[+A] {
|
|
1049
|
+
def mapAccum[S, B](s0: S)(f: (S, A) => (S, B)): (S, Chunk[B])
|
|
1050
|
+
}
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
Accumulating state during mapping threads an accumulator through:
|
|
1054
|
+
|
|
1055
|
+
```scala
|
|
1056
|
+
import zio.blocks.chunk.Chunk
|
|
1057
|
+
|
|
1058
|
+
val nums = Chunk(10, 20, 30)
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
Starting with sum zero, accumulate running totals:
|
|
1062
|
+
|
|
1063
|
+
```scala
|
|
1064
|
+
val (finalSum, result) = nums.mapAccum(0) { (sum, n) => (sum + n, sum + n) }
|
|
1065
|
+
// finalSum: Int = 60
|
|
1066
|
+
// result: Chunk[Int] = IndexedSeq(10, 30, 60)
|
|
1067
|
+
finalSum
|
|
1068
|
+
// res63: Int = 60
|
|
1069
|
+
result
|
|
1070
|
+
// res64: Chunk[Int] = IndexedSeq(10, 30, 60)
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
#### `Chunk#reverse` — Reverse Order
|
|
1074
|
+
|
|
1075
|
+
Reverse the order of elements in the chunk:
|
|
1076
|
+
|
|
1077
|
+
```scala
|
|
1078
|
+
trait Chunk[+A] {
|
|
1079
|
+
def reverse: Chunk[A]
|
|
1080
|
+
}
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1083
|
+
Reversing creates a new chunk with elements in opposite order:
|
|
1084
|
+
|
|
1085
|
+
```scala
|
|
1086
|
+
import zio.blocks.chunk.Chunk
|
|
1087
|
+
|
|
1088
|
+
val original = Chunk(1, 2, 3, 4)
|
|
1089
|
+
// original: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
Reverse produces:
|
|
1093
|
+
|
|
1094
|
+
```scala
|
|
1095
|
+
original.reverse
|
|
1096
|
+
// res66: Chunk[Int] = IndexedSeq(4, 3, 2, 1)
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
### Combining Chunks
|
|
1100
|
+
|
|
1101
|
+
When you need to bring multiple chunks together, Chunk provides efficient operations to merge them. Concatenation uses a balanced tree structure for fast repeated joins, you can append or prepend single elements, and zip operations pair elements from parallel sequences:
|
|
1102
|
+
|
|
1103
|
+
#### `Chunk#++(that)` — Concatenation
|
|
1104
|
+
|
|
1105
|
+
Combine two chunks. Uses balanced tree structure for efficiency:
|
|
1106
|
+
|
|
1107
|
+
```scala
|
|
1108
|
+
trait Chunk[+A] {
|
|
1109
|
+
def ++[A1 >: A](that: Chunk[A1]): Chunk[A1]
|
|
1110
|
+
}
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
Concatenating two chunks combines them efficiently:
|
|
1114
|
+
|
|
1115
|
+
```scala
|
|
1116
|
+
import zio.blocks.chunk.Chunk
|
|
1117
|
+
|
|
1118
|
+
val chunk1 = Chunk(1, 2, 3)
|
|
1119
|
+
// chunk1: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1120
|
+
val chunk2 = Chunk(4, 5, 6)
|
|
1121
|
+
// chunk2: Chunk[Int] = IndexedSeq(4, 5, 6)
|
|
1122
|
+
val combined = chunk1 ++ chunk2
|
|
1123
|
+
// combined: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
|
|
1124
|
+
|
|
1125
|
+
// Efficient even with many concatenations
|
|
1126
|
+
val many = (1 to 100).foldLeft(Chunk.empty[Int]) { (acc, i) =>
|
|
1127
|
+
acc ++ Chunk(i)
|
|
1128
|
+
}
|
|
1129
|
+
// many: Chunk[Int] = IndexedSeq(
|
|
1130
|
+
// 1,
|
|
1131
|
+
// 2,
|
|
1132
|
+
// 3,
|
|
1133
|
+
// 4,
|
|
1134
|
+
// 5,
|
|
1135
|
+
// 6,
|
|
1136
|
+
// 7,
|
|
1137
|
+
// 8,
|
|
1138
|
+
// 9,
|
|
1139
|
+
// 10,
|
|
1140
|
+
// 11,
|
|
1141
|
+
// 12,
|
|
1142
|
+
// 13,
|
|
1143
|
+
// 14,
|
|
1144
|
+
// 15,
|
|
1145
|
+
// 16,
|
|
1146
|
+
// 17,
|
|
1147
|
+
// 18,
|
|
1148
|
+
// 19,
|
|
1149
|
+
// 20,
|
|
1150
|
+
// 21,
|
|
1151
|
+
// 22,
|
|
1152
|
+
// 23,
|
|
1153
|
+
// 24,
|
|
1154
|
+
// 25,
|
|
1155
|
+
// 26,
|
|
1156
|
+
// 27,
|
|
1157
|
+
// 28,
|
|
1158
|
+
// 29,
|
|
1159
|
+
// 30,
|
|
1160
|
+
// 31,
|
|
1161
|
+
// 32,
|
|
1162
|
+
// 33,
|
|
1163
|
+
// 34,
|
|
1164
|
+
// 35,
|
|
1165
|
+
// 36,
|
|
1166
|
+
// 37,
|
|
1167
|
+
// 38,
|
|
1168
|
+
// 39,
|
|
1169
|
+
// 40,
|
|
1170
|
+
// 41,
|
|
1171
|
+
// 42,
|
|
1172
|
+
// 43,
|
|
1173
|
+
// 44,
|
|
1174
|
+
// 45,
|
|
1175
|
+
// 46,
|
|
1176
|
+
// 47,
|
|
1177
|
+
// 48,
|
|
1178
|
+
// ...
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
#### `Chunk#:+(a)` — Append Element
|
|
1182
|
+
|
|
1183
|
+
Append a single element to the end:
|
|
1184
|
+
|
|
1185
|
+
```scala
|
|
1186
|
+
trait Chunk[+A] {
|
|
1187
|
+
def :+[A1 >: A](a: A1): Chunk[A1]
|
|
1188
|
+
}
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
Appending a single element creates a new chunk:
|
|
1192
|
+
|
|
1193
|
+
```scala
|
|
1194
|
+
import zio.blocks.chunk.Chunk
|
|
1195
|
+
|
|
1196
|
+
val chunk = Chunk(1, 2, 3)
|
|
1197
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1198
|
+
val appended = chunk :+ 4
|
|
1199
|
+
// appended: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
#### `Chunk#+:(a)` — Prepend Element
|
|
1203
|
+
|
|
1204
|
+
Prepend a single element to the beginning:
|
|
1205
|
+
|
|
1206
|
+
```scala
|
|
1207
|
+
trait Chunk[+A] {
|
|
1208
|
+
def +:[A1 >: A](a: A1): Chunk[A1]
|
|
1209
|
+
}
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
Prepending a single element adds it to the front:
|
|
1213
|
+
|
|
1214
|
+
```scala
|
|
1215
|
+
import zio.blocks.chunk.Chunk
|
|
1216
|
+
|
|
1217
|
+
val chunk = Chunk(2, 3, 4)
|
|
1218
|
+
// chunk: Chunk[Int] = IndexedSeq(2, 3, 4)
|
|
1219
|
+
val prepended = 1 +: chunk
|
|
1220
|
+
// prepended: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
1221
|
+
```
|
|
1222
|
+
|
|
1223
|
+
#### `Chunk#zip` and `Chunk#zipWith` — Combine Parallel Chunks
|
|
1224
|
+
|
|
1225
|
+
Combine two chunks element-wise:
|
|
1226
|
+
|
|
1227
|
+
```scala
|
|
1228
|
+
trait Chunk[+A] {
|
|
1229
|
+
def zip[B](that: Chunk[B]): Chunk[(A, B)]
|
|
1230
|
+
def zipWith[B, C](that: Chunk[B])(f: (A, B) => C): Chunk[C]
|
|
1231
|
+
}
|
|
1232
|
+
```
|
|
1233
|
+
|
|
1234
|
+
Zipping combines elements from two chunks:
|
|
1235
|
+
|
|
1236
|
+
```scala
|
|
1237
|
+
import zio.blocks.chunk.Chunk
|
|
1238
|
+
|
|
1239
|
+
val numbers = Chunk(1, 2, 3)
|
|
1240
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1241
|
+
val letters = Chunk("a", "b", "c")
|
|
1242
|
+
// letters: Chunk[String] = IndexedSeq("a", "b", "c")
|
|
1243
|
+
|
|
1244
|
+
val zipped = numbers.zip(letters)
|
|
1245
|
+
// zipped: Chunk[Tuple2[Int, String]] = IndexedSeq(
|
|
1246
|
+
// (1, "a"),
|
|
1247
|
+
// (2, "b"),
|
|
1248
|
+
// (3, "c")
|
|
1249
|
+
// )
|
|
1250
|
+
|
|
1251
|
+
val combined = numbers.zipWith(letters)((n, l) => s"$l$n")
|
|
1252
|
+
// combined: Chunk[String] = IndexedSeq("a1", "b2", "c3")
|
|
1253
|
+
```
|
|
1254
|
+
|
|
1255
|
+
#### `Chunk#appended` — Append Element (Method Form)
|
|
1256
|
+
|
|
1257
|
+
Append a single element to the end (named method equivalent of `:+`):
|
|
1258
|
+
|
|
1259
|
+
```scala
|
|
1260
|
+
trait Chunk[+A] {
|
|
1261
|
+
def appended[A1 >: A](a: A1): Chunk[A1]
|
|
1262
|
+
}
|
|
1263
|
+
```
|
|
1264
|
+
|
|
1265
|
+
Appending creates a new chunk:
|
|
1266
|
+
|
|
1267
|
+
```scala
|
|
1268
|
+
import zio.blocks.chunk.Chunk
|
|
1269
|
+
|
|
1270
|
+
val chunk = Chunk(1, 2, 3)
|
|
1271
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1272
|
+
val appended = chunk.appended(4)
|
|
1273
|
+
// appended: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
1274
|
+
```
|
|
1275
|
+
|
|
1276
|
+
#### `Chunk#prepended` — Prepend Element (Method Form)
|
|
1277
|
+
|
|
1278
|
+
Prepend a single element to the beginning (named method equivalent of `+:`):
|
|
1279
|
+
|
|
1280
|
+
```scala
|
|
1281
|
+
trait Chunk[+A] {
|
|
1282
|
+
def prepended[A1 >: A](a: A1): Chunk[A1]
|
|
1283
|
+
}
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
Prepending adds an element to the front:
|
|
1287
|
+
|
|
1288
|
+
```scala
|
|
1289
|
+
import zio.blocks.chunk.Chunk
|
|
1290
|
+
|
|
1291
|
+
val chunk = Chunk(2, 3, 4)
|
|
1292
|
+
// chunk: Chunk[Int] = IndexedSeq(2, 3, 4)
|
|
1293
|
+
val prepended = chunk.prepended(1)
|
|
1294
|
+
// prepended: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
#### `Chunk#zipAll` — Zip with Defaults for Unequal Lengths
|
|
1298
|
+
|
|
1299
|
+
Combine two chunks element-wise, using default values when one chunk is shorter:
|
|
1300
|
+
|
|
1301
|
+
```scala
|
|
1302
|
+
trait Chunk[+A] {
|
|
1303
|
+
def zipAll[B](that: Chunk[B]): Chunk[(Option[A], Option[B])]
|
|
1304
|
+
}
|
|
1305
|
+
```
|
|
1306
|
+
|
|
1307
|
+
Zipping with defaults ensures both sides always have a value:
|
|
1308
|
+
|
|
1309
|
+
```scala
|
|
1310
|
+
import zio.blocks.chunk.Chunk
|
|
1311
|
+
|
|
1312
|
+
val left = Chunk(1, 2, 3)
|
|
1313
|
+
val right = Chunk("a", "b")
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
The shorter chunk is padded with `None` values:
|
|
1317
|
+
|
|
1318
|
+
```scala
|
|
1319
|
+
left.zipAll(right)
|
|
1320
|
+
// res74: Chunk[Tuple2[Option[Int], Option[String]]] = IndexedSeq(
|
|
1321
|
+
// (Some(1), Some("a")),
|
|
1322
|
+
// (Some(2), Some("b")),
|
|
1323
|
+
// (Some(3), None)
|
|
1324
|
+
// )
|
|
1325
|
+
```
|
|
1326
|
+
|
|
1327
|
+
#### `Chunk#zipAllWith` — Zip with Custom Combiner
|
|
1328
|
+
|
|
1329
|
+
Zip two chunks element-wise with a custom combiner function that handles missing elements:
|
|
1330
|
+
|
|
1331
|
+
```scala
|
|
1332
|
+
trait Chunk[+A] {
|
|
1333
|
+
def zipAllWith[B, C](that: Chunk[B])(left: A => C, right: B => C)(both: (A, B) => C): Chunk[C]
|
|
1334
|
+
}
|
|
1335
|
+
```
|
|
1336
|
+
|
|
1337
|
+
Providing separate handlers for left-only, right-only, and both:
|
|
1338
|
+
|
|
1339
|
+
```scala
|
|
1340
|
+
import zio.blocks.chunk.Chunk
|
|
1341
|
+
|
|
1342
|
+
val left = Chunk(1, 2, 3)
|
|
1343
|
+
// left: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1344
|
+
val right = Chunk("a", "b")
|
|
1345
|
+
// right: Chunk[String] = IndexedSeq("a", "b")
|
|
1346
|
+
```
|
|
1347
|
+
|
|
1348
|
+
Combine with default for missing elements:
|
|
1349
|
+
|
|
1350
|
+
```scala
|
|
1351
|
+
left.zipAllWith(right)(
|
|
1352
|
+
a => s"missing:${a}", // left only
|
|
1353
|
+
b => s"only:${b}" // right only
|
|
1354
|
+
)(
|
|
1355
|
+
(a, b) => s"$a:$b" // both present
|
|
1356
|
+
)
|
|
1357
|
+
// res76: Chunk[String] = IndexedSeq("1:a", "2:b", "missing:3")
|
|
1358
|
+
```
|
|
1359
|
+
|
|
1360
|
+
#### `Chunk#zipWithIndex` — Zip with Index Starting at Zero
|
|
1361
|
+
|
|
1362
|
+
Zip each element with its zero-based index:
|
|
1363
|
+
|
|
1364
|
+
```scala
|
|
1365
|
+
trait Chunk[+A] {
|
|
1366
|
+
def zipWithIndex: Chunk[(A, Int)]
|
|
1367
|
+
}
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
Attaching indices is useful for positional awareness:
|
|
1371
|
+
|
|
1372
|
+
```scala
|
|
1373
|
+
import zio.blocks.chunk.Chunk
|
|
1374
|
+
|
|
1375
|
+
val words = Chunk("alpha", "beta", "gamma")
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
Zipping with index:
|
|
1379
|
+
|
|
1380
|
+
```scala
|
|
1381
|
+
words.zipWithIndex
|
|
1382
|
+
// res78: Chunk[Tuple2[String, Int]] = IndexedSeq(
|
|
1383
|
+
// ("alpha", 0),
|
|
1384
|
+
// ("beta", 1),
|
|
1385
|
+
// ("gamma", 2)
|
|
1386
|
+
// )
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
#### `Chunk#updated` — Update Element at Index
|
|
1390
|
+
|
|
1391
|
+
Update the element at a given index, returning a new chunk:
|
|
1392
|
+
|
|
1393
|
+
```scala
|
|
1394
|
+
trait Chunk[+A] {
|
|
1395
|
+
override def updated[A1 >: A](index: Int, elem: A1): Chunk[A1]
|
|
1396
|
+
}
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
The `updated` method creates a new chunk with the element at the specified index replaced. The original chunk remains unchanged:
|
|
1400
|
+
|
|
1401
|
+
```scala
|
|
1402
|
+
import zio.blocks.chunk.Chunk
|
|
1403
|
+
|
|
1404
|
+
val chunk = Chunk(1, 2, 3, 4, 5)
|
|
1405
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
1406
|
+
|
|
1407
|
+
val updated = chunk.updated(2, 99)
|
|
1408
|
+
// updated: Chunk[Int] = IndexedSeq(1, 2, 99, 4, 5)
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
### Slicing and Partitioning
|
|
1412
|
+
|
|
1413
|
+
Often you need to work with portions of a chunk rather than the whole. These operations let you keep elements from the ends, skip unwanted portions, extract contiguous ranges by position, and intelligently partition chunks based on predicates or conditions:
|
|
1414
|
+
|
|
1415
|
+
#### `Chunk#take` and `Chunk#takeRight` — Take from Ends
|
|
1416
|
+
|
|
1417
|
+
Take the first n elements or last n elements:
|
|
1418
|
+
|
|
1419
|
+
```scala
|
|
1420
|
+
trait Chunk[+A] {
|
|
1421
|
+
def take(n: Int): Chunk[A]
|
|
1422
|
+
def takeRight(n: Int): Chunk[A]
|
|
1423
|
+
}
|
|
1424
|
+
```
|
|
1425
|
+
|
|
1426
|
+
Taking elements from the beginning or end creates a new chunk:
|
|
1427
|
+
|
|
1428
|
+
```scala
|
|
1429
|
+
import zio.blocks.chunk.Chunk
|
|
1430
|
+
|
|
1431
|
+
val chunk = Chunk(1, 2, 3, 4, 5)
|
|
1432
|
+
```
|
|
1433
|
+
|
|
1434
|
+
Taking from the beginning or end produces new chunks:
|
|
1435
|
+
|
|
1436
|
+
```scala
|
|
1437
|
+
chunk.take(3)
|
|
1438
|
+
// res81: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1439
|
+
chunk.takeRight(2)
|
|
1440
|
+
// res82: Chunk[Int] = IndexedSeq(4, 5)
|
|
1441
|
+
```
|
|
1442
|
+
|
|
1443
|
+
#### `Chunk#drop` and `Chunk#dropRight` — Remove from Ends
|
|
1444
|
+
|
|
1445
|
+
Remove the first n elements or last n elements:
|
|
1446
|
+
|
|
1447
|
+
```scala
|
|
1448
|
+
trait Chunk[+A] {
|
|
1449
|
+
def drop(n: Int): Chunk[A]
|
|
1450
|
+
def dropRight(n: Int): Chunk[A]
|
|
1451
|
+
}
|
|
1452
|
+
```
|
|
1453
|
+
|
|
1454
|
+
Dropping elements removes them from the beginning or end:
|
|
1455
|
+
|
|
1456
|
+
```scala
|
|
1457
|
+
import zio.blocks.chunk.Chunk
|
|
1458
|
+
|
|
1459
|
+
val chunk = Chunk(1, 2, 3, 4, 5)
|
|
1460
|
+
```
|
|
1461
|
+
|
|
1462
|
+
Dropping from beginning or end removes those elements:
|
|
1463
|
+
|
|
1464
|
+
```scala
|
|
1465
|
+
chunk.drop(2)
|
|
1466
|
+
// res84: Chunk[Int] = IndexedSeq(3, 4, 5)
|
|
1467
|
+
chunk.dropRight(2)
|
|
1468
|
+
// res85: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1469
|
+
```
|
|
1470
|
+
|
|
1471
|
+
#### `Chunk#slice` — Extract a Range
|
|
1472
|
+
|
|
1473
|
+
Extract elements from a start index to an end index:
|
|
1474
|
+
|
|
1475
|
+
```scala
|
|
1476
|
+
trait Chunk[+A] {
|
|
1477
|
+
def slice(from: Int, until: Int): Chunk[A]
|
|
1478
|
+
}
|
|
1479
|
+
```
|
|
1480
|
+
|
|
1481
|
+
Slicing extracts a contiguous range of elements:
|
|
1482
|
+
|
|
1483
|
+
```scala
|
|
1484
|
+
import zio.blocks.chunk.Chunk
|
|
1485
|
+
|
|
1486
|
+
val chunk = Chunk(10, 20, 30, 40, 50, 60)
|
|
1487
|
+
```
|
|
1488
|
+
|
|
1489
|
+
Slicing from a start index to end index extracts the range:
|
|
1490
|
+
|
|
1491
|
+
```scala
|
|
1492
|
+
chunk.slice(1, 4)
|
|
1493
|
+
// res87: Chunk[Int] = IndexedSeq(20, 30, 40)
|
|
1494
|
+
chunk.slice(2, 5)
|
|
1495
|
+
// res88: Chunk[Int] = IndexedSeq(30, 40, 50)
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
#### `Chunk#split` — Split into Equally-Sized Chunks
|
|
1499
|
+
|
|
1500
|
+
Split the chunk into N equally-sized chunks. When the chunk size is not evenly divisible, remainder elements are distributed into earlier chunks. If n exceeds the chunk's length, the result contains at most chunk.length single-element chunks.
|
|
1501
|
+
|
|
1502
|
+
Note: Passing `0` throws `ArithmeticException` due to division by zero. Negative values are not rejected by the current implementation and may produce unexpected results, so `n` should always be positive in normal use.
|
|
1503
|
+
|
|
1504
|
+
The method signature is:
|
|
1505
|
+
|
|
1506
|
+
```scala
|
|
1507
|
+
trait Chunk[+A] {
|
|
1508
|
+
def split(n: Int): Chunk[Chunk[A]]
|
|
1509
|
+
}
|
|
1510
|
+
```
|
|
1511
|
+
|
|
1512
|
+
Splitting divides a chunk into N equal parts:
|
|
1513
|
+
|
|
1514
|
+
```scala
|
|
1515
|
+
import zio.blocks.chunk.Chunk
|
|
1516
|
+
|
|
1517
|
+
val chunk = Chunk(1, 2, 3, 4, 5, 6)
|
|
1518
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
|
|
1519
|
+
val splitInto2 = chunk.split(2)
|
|
1520
|
+
// splitInto2: Chunk[Chunk[Int]] = IndexedSeq(
|
|
1521
|
+
// IndexedSeq(1, 2, 3),
|
|
1522
|
+
// IndexedSeq(4, 5, 6)
|
|
1523
|
+
// )
|
|
1524
|
+
|
|
1525
|
+
val uneven = Chunk(1, 2, 3, 4, 5, 6, 7)
|
|
1526
|
+
// uneven: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6, 7)
|
|
1527
|
+
val splitInto3 = uneven.split(3)
|
|
1528
|
+
// splitInto3: Chunk[Chunk[Int]] = IndexedSeq(
|
|
1529
|
+
// IndexedSeq(1, 2, 3),
|
|
1530
|
+
// IndexedSeq(4, 5),
|
|
1531
|
+
// IndexedSeq(6, 7)
|
|
1532
|
+
// )
|
|
1533
|
+
```
|
|
1534
|
+
|
|
1535
|
+
#### `Chunk#span` and `Chunk#splitWhere` — Partition by Predicate
|
|
1536
|
+
|
|
1537
|
+
`span(f)` splits the chunk into a prefix where the predicate holds, and the remainder. It stops at the first element where the predicate becomes false.
|
|
1538
|
+
|
|
1539
|
+
`splitWhere(f)` is similar but has inverted logic: it splits at the first element where the predicate becomes true.
|
|
1540
|
+
|
|
1541
|
+
The method signatures are:
|
|
1542
|
+
|
|
1543
|
+
```scala
|
|
1544
|
+
trait Chunk[+A] {
|
|
1545
|
+
def span(f: A => Boolean): (Chunk[A], Chunk[A])
|
|
1546
|
+
def splitWhere(f: A => Boolean): (Chunk[A], Chunk[A])
|
|
1547
|
+
}
|
|
1548
|
+
```
|
|
1549
|
+
|
|
1550
|
+
Partitioning by predicate creates two chunks:
|
|
1551
|
+
|
|
1552
|
+
```scala
|
|
1553
|
+
import zio.blocks.chunk.Chunk
|
|
1554
|
+
|
|
1555
|
+
val numbers = Chunk(1, 2, 3, 4, 5, 6)
|
|
1556
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
|
|
1557
|
+
val (prefix, rest) = numbers.span(_ < 4)
|
|
1558
|
+
// prefix: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1559
|
+
// rest: Chunk[Int] = IndexedSeq(4, 5, 6)
|
|
1560
|
+
val (upTo, remaining) = Chunk(1, 2, 5, 3, 4).splitWhere(_ >= 5)
|
|
1561
|
+
// upTo: Chunk[Int] = IndexedSeq(1, 2)
|
|
1562
|
+
// remaining: Chunk[Int] = IndexedSeq(5, 3, 4)
|
|
1563
|
+
```
|
|
1564
|
+
|
|
1565
|
+
#### `Chunk#dropWhile` — Drop While Predicate Holds
|
|
1566
|
+
|
|
1567
|
+
Drop elements from the beginning while a predicate is true:
|
|
1568
|
+
|
|
1569
|
+
```scala
|
|
1570
|
+
trait Chunk[+A] {
|
|
1571
|
+
def dropWhile(f: A => Boolean): Chunk[A]
|
|
1572
|
+
}
|
|
1573
|
+
```
|
|
1574
|
+
|
|
1575
|
+
Dropping while the predicate holds removes leading elements:
|
|
1576
|
+
|
|
1577
|
+
```scala
|
|
1578
|
+
import zio.blocks.chunk.Chunk
|
|
1579
|
+
|
|
1580
|
+
val numbers = Chunk(1, 2, 3, 4, 5, 6)
|
|
1581
|
+
```
|
|
1582
|
+
|
|
1583
|
+
Drop initial even numbers:
|
|
1584
|
+
|
|
1585
|
+
```scala
|
|
1586
|
+
numbers.dropWhile(_ % 2 == 0)
|
|
1587
|
+
// res92: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6)
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
#### `Chunk#dropUntil` — Drop Until Predicate Fails
|
|
1591
|
+
|
|
1592
|
+
Drop elements until the predicate becomes false (i.e., drop while predicate holds, but stopped when condition fails):
|
|
1593
|
+
|
|
1594
|
+
```scala
|
|
1595
|
+
trait Chunk[+A] {
|
|
1596
|
+
def dropUntil(f: A => Boolean): Chunk[A]
|
|
1597
|
+
}
|
|
1598
|
+
```
|
|
1599
|
+
|
|
1600
|
+
Dropping until the predicate fails keeps the first failing element and the rest:
|
|
1601
|
+
|
|
1602
|
+
```scala
|
|
1603
|
+
import zio.blocks.chunk.Chunk
|
|
1604
|
+
|
|
1605
|
+
val numbers = Chunk(1, 2, 3, 4, 5)
|
|
1606
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
1607
|
+
```
|
|
1608
|
+
|
|
1609
|
+
Drop numbers less than 3:
|
|
1610
|
+
|
|
1611
|
+
```scala
|
|
1612
|
+
numbers.dropUntil(_ >= 3)
|
|
1613
|
+
// res94: Chunk[Int] = IndexedSeq(4, 5)
|
|
1614
|
+
```
|
|
1615
|
+
|
|
1616
|
+
#### `Chunk#takeWhile` — Take While Predicate Holds
|
|
1617
|
+
|
|
1618
|
+
Take elements from the beginning while a predicate is true, stopping at the first failure:
|
|
1619
|
+
|
|
1620
|
+
```scala
|
|
1621
|
+
trait Chunk[+A] {
|
|
1622
|
+
def takeWhile(f: A => Boolean): Chunk[A]
|
|
1623
|
+
}
|
|
1624
|
+
```
|
|
1625
|
+
|
|
1626
|
+
Taking while the predicate holds collects a prefix:
|
|
1627
|
+
|
|
1628
|
+
```scala
|
|
1629
|
+
import zio.blocks.chunk.Chunk
|
|
1630
|
+
|
|
1631
|
+
val numbers = Chunk(1, 2, 3, 4, 5, 6)
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
Take the initial odd numbers:
|
|
1635
|
+
|
|
1636
|
+
```scala
|
|
1637
|
+
numbers.takeWhile(_ % 2 != 0)
|
|
1638
|
+
// res96: Chunk[Int] = IndexedSeq(1)
|
|
1639
|
+
```
|
|
1640
|
+
|
|
1641
|
+
#### `Chunk#splitAt` — Split at Index
|
|
1642
|
+
|
|
1643
|
+
Split the chunk into two chunks at a given index: the first contains elements `[0, until)`, the second contains the remainder.
|
|
1644
|
+
|
|
1645
|
+
The method signature is:
|
|
1646
|
+
|
|
1647
|
+
```scala
|
|
1648
|
+
trait Chunk[+A] {
|
|
1649
|
+
def splitAt(n: Int): (Chunk[A], Chunk[A])
|
|
1650
|
+
}
|
|
1651
|
+
```
|
|
1652
|
+
|
|
1653
|
+
Splitting creates two chunks from a single index:
|
|
1654
|
+
|
|
1655
|
+
```scala
|
|
1656
|
+
import zio.blocks.chunk.Chunk
|
|
1657
|
+
|
|
1658
|
+
val chunk = Chunk(10, 20, 30, 40, 50)
|
|
1659
|
+
```
|
|
1660
|
+
|
|
1661
|
+
Splitting at index 3:
|
|
1662
|
+
|
|
1663
|
+
```scala
|
|
1664
|
+
chunk.splitAt(3)
|
|
1665
|
+
// res98: Tuple2[Chunk[Int], Chunk[Int]] = (
|
|
1666
|
+
// IndexedSeq(10, 20, 30),
|
|
1667
|
+
// IndexedSeq(40, 50)
|
|
1668
|
+
// )
|
|
1669
|
+
```
|
|
1670
|
+
|
|
1671
|
+
### Querying and Folding
|
|
1672
|
+
|
|
1673
|
+
Chunk enables you to ask questions about your data and reduce it to meaningful results. Use fold operations to accumulate values, test predicates to verify conditions hold across elements, and search for specific values that match your criteria:
|
|
1674
|
+
|
|
1675
|
+
#### `Chunk#foldLeft` — Left Fold
|
|
1676
|
+
|
|
1677
|
+
Process elements left-to-right with an accumulator:
|
|
1678
|
+
|
|
1679
|
+
```scala
|
|
1680
|
+
trait Chunk[+A] {
|
|
1681
|
+
def foldLeft[S](s0: S)(f: (S, A) => S): S
|
|
1682
|
+
}
|
|
1683
|
+
```
|
|
1684
|
+
|
|
1685
|
+
Left-folding accumulates values from left to right:
|
|
1686
|
+
|
|
1687
|
+
```scala
|
|
1688
|
+
import zio.blocks.chunk.Chunk
|
|
1689
|
+
|
|
1690
|
+
val numbers = Chunk(1, 2, 3, 4, 5)
|
|
1691
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
1692
|
+
|
|
1693
|
+
val sum = numbers.foldLeft(0)(_ + _)
|
|
1694
|
+
// sum: Int = 15
|
|
1695
|
+
|
|
1696
|
+
val product = numbers.foldLeft(1)(_ * _)
|
|
1697
|
+
// product: Int = 120
|
|
1698
|
+
|
|
1699
|
+
val concat = Chunk("a", "b", "c").foldLeft("")(_ + _)
|
|
1700
|
+
// concat: String = "abc"
|
|
1701
|
+
```
|
|
1702
|
+
|
|
1703
|
+
#### `Chunk#foldRight` — Right Fold
|
|
1704
|
+
|
|
1705
|
+
Process elements right-to-left with an accumulator:
|
|
1706
|
+
|
|
1707
|
+
```scala
|
|
1708
|
+
trait Chunk[+A] {
|
|
1709
|
+
def foldRight[S](s0: S)(f: (A, S) => S): S
|
|
1710
|
+
}
|
|
1711
|
+
```
|
|
1712
|
+
|
|
1713
|
+
Right-folding accumulates values from right to left:
|
|
1714
|
+
|
|
1715
|
+
```scala
|
|
1716
|
+
import zio.blocks.chunk.Chunk
|
|
1717
|
+
|
|
1718
|
+
val numbers = Chunk(1, 2, 3, 4)
|
|
1719
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
1720
|
+
val result = numbers.foldRight(List[Int]())(_ :: _)
|
|
1721
|
+
// result: List[Int] = List(1, 2, 3, 4)
|
|
1722
|
+
```
|
|
1723
|
+
|
|
1724
|
+
#### `Chunk#foldWhile` — Fold with Early Exit
|
|
1725
|
+
|
|
1726
|
+
Fold left with early termination based on a predicate on the accumulator:
|
|
1727
|
+
|
|
1728
|
+
```scala
|
|
1729
|
+
trait Chunk[+A] {
|
|
1730
|
+
def foldWhile[S](s0: S)(pred: S => Boolean)(f: (S, A) => S): S
|
|
1731
|
+
}
|
|
1732
|
+
```
|
|
1733
|
+
|
|
1734
|
+
`foldWhile` allows you to stop folding when a condition on the accumulator becomes false, avoiding unnecessary processing of remaining elements:
|
|
1735
|
+
|
|
1736
|
+
```scala
|
|
1737
|
+
import zio.blocks.chunk.Chunk
|
|
1738
|
+
|
|
1739
|
+
val numbers = Chunk(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
|
1740
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
|
1741
|
+
|
|
1742
|
+
// Fold while accumulator is less than 20, adding elements
|
|
1743
|
+
val result = numbers.foldWhile(0)(acc => acc < 20)((acc, n) => acc + n)
|
|
1744
|
+
// result: Int = 21
|
|
1745
|
+
```
|
|
1746
|
+
|
|
1747
|
+
#### `Chunk#exists` and `Chunk#forall` — Predicates
|
|
1748
|
+
|
|
1749
|
+
Check if any or all elements match a predicate:
|
|
1750
|
+
|
|
1751
|
+
```scala
|
|
1752
|
+
trait Chunk[+A] {
|
|
1753
|
+
def exists(f: A => Boolean): Boolean
|
|
1754
|
+
def forall(f: A => Boolean): Boolean
|
|
1755
|
+
}
|
|
1756
|
+
```
|
|
1757
|
+
|
|
1758
|
+
Testing predicates answers questions about elements:
|
|
1759
|
+
|
|
1760
|
+
```scala
|
|
1761
|
+
import zio.blocks.chunk.Chunk
|
|
1762
|
+
|
|
1763
|
+
val numbers = Chunk(2, 4, 6, 8)
|
|
1764
|
+
val mixed = Chunk(1, 2, 3)
|
|
1765
|
+
```
|
|
1766
|
+
|
|
1767
|
+
Testing existence and universal predicates returns Boolean:
|
|
1768
|
+
|
|
1769
|
+
```scala
|
|
1770
|
+
numbers.exists(_ > 5)
|
|
1771
|
+
// res103: Boolean = true
|
|
1772
|
+
numbers.forall(_ % 2 == 0)
|
|
1773
|
+
// res104: Boolean = true
|
|
1774
|
+
mixed.forall(_ > 0)
|
|
1775
|
+
// res105: Boolean = true
|
|
1776
|
+
mixed.forall(_ % 2 == 0)
|
|
1777
|
+
// res106: Boolean = false
|
|
1778
|
+
```
|
|
1779
|
+
|
|
1780
|
+
#### `Chunk#find` — First Matching Element
|
|
1781
|
+
|
|
1782
|
+
Find the first element matching a predicate:
|
|
1783
|
+
|
|
1784
|
+
```scala
|
|
1785
|
+
trait Chunk[+A] {
|
|
1786
|
+
def find(f: A => Boolean): Option[A]
|
|
1787
|
+
}
|
|
1788
|
+
```
|
|
1789
|
+
|
|
1790
|
+
Finding the first matching element returns an Option:
|
|
1791
|
+
|
|
1792
|
+
```scala
|
|
1793
|
+
import zio.blocks.chunk.Chunk
|
|
1794
|
+
|
|
1795
|
+
val numbers = Chunk(1, 2, 3, 4, 5)
|
|
1796
|
+
val words = Chunk("apple", "banana", "cherry")
|
|
1797
|
+
```
|
|
1798
|
+
|
|
1799
|
+
Finding returns Some for matches and None for no match:
|
|
1800
|
+
|
|
1801
|
+
```scala
|
|
1802
|
+
numbers.find(_ > 3)
|
|
1803
|
+
// res108: Option[Int] = Some(4)
|
|
1804
|
+
numbers.find(_ > 10)
|
|
1805
|
+
// res109: Option[Int] = None
|
|
1806
|
+
words.find(_.startsWith("b"))
|
|
1807
|
+
// res110: Option[String] = Some("banana")
|
|
1808
|
+
```
|
|
1809
|
+
|
|
1810
|
+
#### `Chunk#corresponds` — Check Parallel Correspondence
|
|
1811
|
+
|
|
1812
|
+
Check whether corresponding elements of two chunks satisfy a predicate in lockstep:
|
|
1813
|
+
|
|
1814
|
+
```scala
|
|
1815
|
+
trait Chunk[+A] {
|
|
1816
|
+
def corresponds[B](that: Chunk[B])(f: (A, B) => Boolean): Boolean
|
|
1817
|
+
}
|
|
1818
|
+
```
|
|
1819
|
+
|
|
1820
|
+
`corresponds` tests pairwise element compatibility:
|
|
1821
|
+
|
|
1822
|
+
```scala
|
|
1823
|
+
import zio.blocks.chunk.Chunk
|
|
1824
|
+
|
|
1825
|
+
val a = Chunk(1, 2, 3)
|
|
1826
|
+
val b = Chunk(1, 2, 3)
|
|
1827
|
+
```
|
|
1828
|
+
|
|
1829
|
+
Correspondence holds when all pairs match:
|
|
1830
|
+
|
|
1831
|
+
```scala
|
|
1832
|
+
a.corresponds(b)(_ == _)
|
|
1833
|
+
// res112: Boolean = true
|
|
1834
|
+
```
|
|
1835
|
+
|
|
1836
|
+
### Grouping
|
|
1837
|
+
|
|
1838
|
+
Chunk provides powerful grouping operations to organize elements by keys or into fixed-size partitions. Use `groupBy` to categorize elements into maps of chunks, `groupMap` to combine grouping and mapping in a single pass, and `grouped` to split the chunk into chunks of a specified size.
|
|
1839
|
+
|
|
1840
|
+
#### `Chunk#groupBy` — Group Elements by Key
|
|
1841
|
+
|
|
1842
|
+
Group elements by a key function, producing a map from keys to chunks:
|
|
1843
|
+
|
|
1844
|
+
```scala
|
|
1845
|
+
trait Chunk[+A] {
|
|
1846
|
+
def groupBy[K](f: A => K): Map[K, Chunk[A]]
|
|
1847
|
+
}
|
|
1848
|
+
```
|
|
1849
|
+
|
|
1850
|
+
Grouping by a key aggregates related elements:
|
|
1851
|
+
|
|
1852
|
+
```scala
|
|
1853
|
+
import zio.blocks.chunk.Chunk
|
|
1854
|
+
|
|
1855
|
+
case class Person(name: String, age: Int)
|
|
1856
|
+
val people = Chunk(Person("Alice", 32), Person("Bob", 25), Person("Carol", 32))
|
|
1857
|
+
```
|
|
1858
|
+
|
|
1859
|
+
Group people by age:
|
|
1860
|
+
|
|
1861
|
+
```scala
|
|
1862
|
+
people.groupBy(_.age)
|
|
1863
|
+
// res114: Map[Int, Chunk[Person]] = Map(
|
|
1864
|
+
// 25 -> IndexedSeq(Person(name = "Bob", age = 25)),
|
|
1865
|
+
// 32 -> IndexedSeq(Person(name = "Alice", age = 32), Person(name = "Carol", age = 32))
|
|
1866
|
+
// )
|
|
1867
|
+
```
|
|
1868
|
+
|
|
1869
|
+
#### `Chunk#groupMap` — Group and Map in One Pass
|
|
1870
|
+
|
|
1871
|
+
Group elements by a key and simultaneously transform the values:
|
|
1872
|
+
|
|
1873
|
+
```scala
|
|
1874
|
+
trait Chunk[+A] {
|
|
1875
|
+
def groupMap[K, V](key: A => K)(f: A => V): Map[K, Chunk[V]]
|
|
1876
|
+
}
|
|
1877
|
+
```
|
|
1878
|
+
|
|
1879
|
+
Grouping and mapping in a single operation can be more efficient than separate steps:
|
|
1880
|
+
|
|
1881
|
+
```scala
|
|
1882
|
+
import zio.blocks.chunk.Chunk
|
|
1883
|
+
|
|
1884
|
+
val words = Chunk("apple", "banana", "cherry", "avocado")
|
|
1885
|
+
// words: Chunk[String] = IndexedSeq("apple", "banana", "cherry", "avocado")
|
|
1886
|
+
```
|
|
1887
|
+
|
|
1888
|
+
Group words by their first letter and also uppercase them:
|
|
1889
|
+
|
|
1890
|
+
```scala
|
|
1891
|
+
words.groupMap(_.head)(_.toUpperCase)
|
|
1892
|
+
// res116: Map[Char, Chunk[String]] = Map(
|
|
1893
|
+
// 'a' -> IndexedSeq("APPLE", "AVOCADO"),
|
|
1894
|
+
// 'b' -> IndexedSeq("BANANA"),
|
|
1895
|
+
// 'c' -> IndexedSeq("CHERRY")
|
|
1896
|
+
// )
|
|
1897
|
+
```
|
|
1898
|
+
|
|
1899
|
+
#### `Chunk#grouped` — Partition into Fixed-Size Groups
|
|
1900
|
+
|
|
1901
|
+
Divide the chunk into groups of a fixed size, returning an iterator over subgroups:
|
|
1902
|
+
|
|
1903
|
+
```scala
|
|
1904
|
+
trait Chunk[+A] {
|
|
1905
|
+
def grouped(size: Int): Iterator[Chunk[A]]
|
|
1906
|
+
}
|
|
1907
|
+
```
|
|
1908
|
+
|
|
1909
|
+
Fixed-size grouping is useful for batch processing:
|
|
1910
|
+
|
|
1911
|
+
```scala
|
|
1912
|
+
import zio.blocks.chunk.Chunk
|
|
1913
|
+
|
|
1914
|
+
val numbers = Chunk(1, 2, 3, 4, 5, 6, 7, 8)
|
|
1915
|
+
```
|
|
1916
|
+
|
|
1917
|
+
Groups of size 3 yield two full groups and a partial:
|
|
1918
|
+
|
|
1919
|
+
```scala
|
|
1920
|
+
numbers.grouped(3).toList
|
|
1921
|
+
// res118: List[Chunk[Int]] = List(
|
|
1922
|
+
// IndexedSeq(1, 2, 3),
|
|
1923
|
+
// IndexedSeq(4, 5, 6),
|
|
1924
|
+
// IndexedSeq(7, 8)
|
|
1925
|
+
// )
|
|
1926
|
+
```
|
|
1927
|
+
|
|
1928
|
+
### Equality and Iteration
|
|
1929
|
+
|
|
1930
|
+
Chunks support standard equality comparison and side-effect iteration:
|
|
1931
|
+
|
|
1932
|
+
#### `Chunk#equals` — Equality Comparison
|
|
1933
|
+
|
|
1934
|
+
Compare two chunks for equality:
|
|
1935
|
+
|
|
1936
|
+
```scala
|
|
1937
|
+
trait Chunk[+A] {
|
|
1938
|
+
override def equals(that: Any): Boolean
|
|
1939
|
+
}
|
|
1940
|
+
```
|
|
1941
|
+
|
|
1942
|
+
Chunks are equal if they contain the same elements in the same order. The comparison works correctly regardless of the internal representation (array-backed vs. tree-structured):
|
|
1943
|
+
|
|
1944
|
+
```scala
|
|
1945
|
+
import zio.blocks.chunk.Chunk
|
|
1946
|
+
|
|
1947
|
+
val chunk1 = Chunk(1, 2, 3)
|
|
1948
|
+
// chunk1: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1949
|
+
val chunk2 = Chunk(1, 2, 3)
|
|
1950
|
+
// chunk2: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1951
|
+
val chunk3 = Chunk(1, 2, 4)
|
|
1952
|
+
// chunk3: Chunk[Int] = IndexedSeq(1, 2, 4)
|
|
1953
|
+
|
|
1954
|
+
chunk1 == chunk2
|
|
1955
|
+
// res120: Boolean = true
|
|
1956
|
+
chunk1 == chunk3
|
|
1957
|
+
// res121: Boolean = false
|
|
1958
|
+
```
|
|
1959
|
+
|
|
1960
|
+
#### `Chunk#hashCode` — Hash Code Computation
|
|
1961
|
+
|
|
1962
|
+
Get the hash code of a chunk:
|
|
1963
|
+
|
|
1964
|
+
```scala
|
|
1965
|
+
trait Chunk[+A] {
|
|
1966
|
+
override def hashCode: Int
|
|
1967
|
+
}
|
|
1968
|
+
```
|
|
1969
|
+
|
|
1970
|
+
Chunks compute their hash code using the MurmurHash3 algorithm applied to all elements, similar to Scala's collection hashing. Equal chunks always have equal hash codes, making chunks suitable for use as keys in hash-based collections:
|
|
1971
|
+
|
|
1972
|
+
```scala
|
|
1973
|
+
import zio.blocks.chunk.Chunk
|
|
1974
|
+
|
|
1975
|
+
val chunk1 = Chunk(1, 2, 3)
|
|
1976
|
+
// chunk1: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1977
|
+
val chunk2 = Chunk(1, 2, 3)
|
|
1978
|
+
// chunk2: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
1979
|
+
|
|
1980
|
+
chunk1.hashCode == chunk2.hashCode
|
|
1981
|
+
// res123: Boolean = true
|
|
1982
|
+
|
|
1983
|
+
val chunkSet = Set(chunk1)
|
|
1984
|
+
// chunkSet: Set[Chunk[Int]] = Set(IndexedSeq(1, 2, 3))
|
|
1985
|
+
chunkSet.contains(chunk2)
|
|
1986
|
+
// res124: Boolean = true
|
|
1987
|
+
```
|
|
1988
|
+
|
|
1989
|
+
Chunks can be reliably used in Maps and Sets because equal chunks have equal hash codes.
|
|
1990
|
+
|
|
1991
|
+
#### `Chunk#foreach` — Iteration with Side Effects
|
|
1992
|
+
|
|
1993
|
+
Perform a side effect for each element (used with `Unit`-returning functions):
|
|
1994
|
+
|
|
1995
|
+
```scala
|
|
1996
|
+
trait Chunk[+A] {
|
|
1997
|
+
override def foreach[B](f: A => B): Unit
|
|
1998
|
+
}
|
|
1999
|
+
```
|
|
2000
|
+
|
|
2001
|
+
`foreach` iterates through all elements and applies a function, ignoring the return value. This is useful for side effects like logging or printing:
|
|
2002
|
+
|
|
2003
|
+
```scala
|
|
2004
|
+
import zio.blocks.chunk.Chunk
|
|
2005
|
+
|
|
2006
|
+
val chunk = Chunk(1, 2, 3)
|
|
2007
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
2008
|
+
|
|
2009
|
+
// Print each element
|
|
2010
|
+
chunk.foreach(n => println(s"Element: $n"))
|
|
2011
|
+
// Element: 1
|
|
2012
|
+
// Element: 2
|
|
2013
|
+
// Element: 3
|
|
2014
|
+
```
|
|
2015
|
+
|
|
2016
|
+
### Conversion
|
|
2017
|
+
|
|
2018
|
+
Sometimes you need to move data from Chunk into other Scala collections or represent it as text. These conversion operations make it easy to export your chunk into arrays, lists, sequences, or render it as a string for logging and display:
|
|
2019
|
+
|
|
2020
|
+
#### `Chunk#toArray` — To `Array`
|
|
2021
|
+
|
|
2022
|
+
Convert to an array:
|
|
2023
|
+
|
|
2024
|
+
```scala
|
|
2025
|
+
trait Chunk[+A] {
|
|
2026
|
+
def toArray[A1 >: A: ClassTag]: Array[A1]
|
|
2027
|
+
}
|
|
2028
|
+
```
|
|
2029
|
+
|
|
2030
|
+
Converting to an array materializes all elements:
|
|
2031
|
+
|
|
2032
|
+
```scala
|
|
2033
|
+
import zio.blocks.chunk.Chunk
|
|
2034
|
+
|
|
2035
|
+
val chunk = Chunk(1, 2, 3, 4)
|
|
2036
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
2037
|
+
val array: Array[Int] = chunk.toArray
|
|
2038
|
+
// array: Array[Int] = Array(1, 2, 3, 4)
|
|
2039
|
+
```
|
|
2040
|
+
|
|
2041
|
+
#### `Chunk#toList` — To `List`
|
|
2042
|
+
|
|
2043
|
+
Convert to a `List`:
|
|
2044
|
+
|
|
2045
|
+
```scala
|
|
2046
|
+
trait Chunk[+A] {
|
|
2047
|
+
def toList: List[A]
|
|
2048
|
+
}
|
|
2049
|
+
```
|
|
2050
|
+
|
|
2051
|
+
Converting to a list produces a sequential data structure:
|
|
2052
|
+
|
|
2053
|
+
```scala
|
|
2054
|
+
import zio.blocks.chunk.Chunk
|
|
2055
|
+
|
|
2056
|
+
val chunk = Chunk("a", "b", "c")
|
|
2057
|
+
// chunk: Chunk[String] = IndexedSeq("a", "b", "c")
|
|
2058
|
+
val list = chunk.toList
|
|
2059
|
+
// list: List[String] = List("a", "b", "c")
|
|
2060
|
+
```
|
|
2061
|
+
|
|
2062
|
+
#### `Chunk#toSeq`, `Chunk#toIterable`, `Chunk#toIndexedSeq` — Standard Collections
|
|
2063
|
+
|
|
2064
|
+
Convert to various Scala collection types:
|
|
2065
|
+
|
|
2066
|
+
```scala
|
|
2067
|
+
trait Chunk[+A] {
|
|
2068
|
+
def toSeq: Seq[A]
|
|
2069
|
+
def toIterable: Iterable[A]
|
|
2070
|
+
def toIndexedSeq: IndexedSeq[A]
|
|
2071
|
+
}
|
|
2072
|
+
```
|
|
2073
|
+
|
|
2074
|
+
Converting to standard Scala collections enables interop:
|
|
2075
|
+
|
|
2076
|
+
```scala
|
|
2077
|
+
import zio.blocks.chunk.Chunk
|
|
2078
|
+
|
|
2079
|
+
val chunk = Chunk(1, 2, 3)
|
|
2080
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
2081
|
+
|
|
2082
|
+
chunk.toSeq
|
|
2083
|
+
// res130: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
2084
|
+
chunk.toIndexedSeq
|
|
2085
|
+
// res131: IndexedSeq[Int] = IndexedSeq(1, 2, 3)
|
|
2086
|
+
```
|
|
2087
|
+
|
|
2088
|
+
#### `Chunk#toString` — String Representation
|
|
2089
|
+
|
|
2090
|
+
Convert to a string:
|
|
2091
|
+
|
|
2092
|
+
```scala
|
|
2093
|
+
trait Chunk[+A] {
|
|
2094
|
+
def toString: String
|
|
2095
|
+
}
|
|
2096
|
+
```
|
|
2097
|
+
|
|
2098
|
+
String representation shows all elements in a compact format:
|
|
2099
|
+
|
|
2100
|
+
```scala
|
|
2101
|
+
import zio.blocks.chunk.Chunk
|
|
2102
|
+
|
|
2103
|
+
val chunk = Chunk(1, 2, 3)
|
|
2104
|
+
```
|
|
2105
|
+
|
|
2106
|
+
String conversion shows the chunk contents:
|
|
2107
|
+
|
|
2108
|
+
```scala
|
|
2109
|
+
chunk.toString
|
|
2110
|
+
// res133: String = "Chunk(1,2,3)"
|
|
2111
|
+
```
|
|
2112
|
+
|
|
2113
|
+
#### `Chunk#toVector` — To `Vector`
|
|
2114
|
+
|
|
2115
|
+
Convert to a `Vector`:
|
|
2116
|
+
|
|
2117
|
+
```scala
|
|
2118
|
+
trait Chunk[+A] {
|
|
2119
|
+
def toVector: Vector[A]
|
|
2120
|
+
}
|
|
2121
|
+
```
|
|
2122
|
+
|
|
2123
|
+
Conversion to `Vector` produces an efficient indexed sequence:
|
|
2124
|
+
|
|
2125
|
+
```scala
|
|
2126
|
+
import zio.blocks.chunk.Chunk
|
|
2127
|
+
|
|
2128
|
+
val chunk = Chunk(1, 2, 3)
|
|
2129
|
+
```
|
|
2130
|
+
|
|
2131
|
+
```scala
|
|
2132
|
+
chunk.toVector
|
|
2133
|
+
// res135: Vector[Int] = Vector(1, 2, 3)
|
|
2134
|
+
```
|
|
2135
|
+
|
|
2136
|
+
### Specialized Accessors for Primitive Types
|
|
2137
|
+
|
|
2138
|
+
For primitive types, direct accessors avoid boxing:
|
|
2139
|
+
|
|
2140
|
+
```scala
|
|
2141
|
+
trait Chunk[+A] {
|
|
2142
|
+
def byte(index: Int)(implicit ev: A <:< Byte): Byte
|
|
2143
|
+
def boolean(index: Int)(implicit ev: A <:< Boolean): Boolean
|
|
2144
|
+
def char(index: Int)(implicit ev: A <:< Char): Char
|
|
2145
|
+
def double(index: Int)(implicit ev: A <:< Double): Double
|
|
2146
|
+
def float(index: Int)(implicit ev: A <:< Float): Float
|
|
2147
|
+
def int(index: Int)(implicit ev: A <:< Int): Int
|
|
2148
|
+
def long(index: Int)(implicit ev: A <:< Long): Long
|
|
2149
|
+
def short(index: Int)(implicit ev: A <:< Short): Short
|
|
2150
|
+
}
|
|
2151
|
+
```
|
|
2152
|
+
|
|
2153
|
+
Accessing primitives without boxing demonstrates zero-overhead specialization:
|
|
2154
|
+
|
|
2155
|
+
```scala
|
|
2156
|
+
import zio.blocks.chunk.Chunk
|
|
2157
|
+
|
|
2158
|
+
val bytes: Chunk[Byte] = Chunk(1.toByte, 2.toByte, 3.toByte)
|
|
2159
|
+
val ints = Chunk(10, 20, 30)
|
|
2160
|
+
```
|
|
2161
|
+
|
|
2162
|
+
Accessing primitives by specialized methods avoids boxing:
|
|
2163
|
+
|
|
2164
|
+
```scala
|
|
2165
|
+
bytes.byte(0)
|
|
2166
|
+
// res137: Byte = 1
|
|
2167
|
+
ints.int(1)
|
|
2168
|
+
// res138: Int = 20
|
|
2169
|
+
```
|
|
2170
|
+
|
|
2171
|
+
### Iterator Access
|
|
2172
|
+
|
|
2173
|
+
Access chunks through iterators for sequential processing or collect the known size of elements:
|
|
2174
|
+
|
|
2175
|
+
#### `Chunk#iterator` — Get a Standard Iterator
|
|
2176
|
+
|
|
2177
|
+
Get a standard Scala `Iterator` to traverse the chunk sequentially:
|
|
2178
|
+
|
|
2179
|
+
```scala
|
|
2180
|
+
trait Chunk[+A] {
|
|
2181
|
+
def iterator: Iterator[A]
|
|
2182
|
+
}
|
|
2183
|
+
```
|
|
2184
|
+
|
|
2185
|
+
The iterator is useful for sequential processing and integrates with Scala's collection ecosystem:
|
|
2186
|
+
|
|
2187
|
+
```scala
|
|
2188
|
+
import zio.blocks.chunk.Chunk
|
|
2189
|
+
|
|
2190
|
+
val chunk = Chunk(1, 2, 3, 4, 5)
|
|
2191
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
2192
|
+
val iter = chunk.iterator
|
|
2193
|
+
// iter: Iterator[Int] = non-empty iterator
|
|
2194
|
+
|
|
2195
|
+
// Process elements sequentially
|
|
2196
|
+
iter.take(3).toList
|
|
2197
|
+
// res140: List[Int] = List(1, 2, 3)
|
|
2198
|
+
```
|
|
2199
|
+
|
|
2200
|
+
#### `Chunk#chunkIterator` — Get a Chunk-Specific Iterator
|
|
2201
|
+
|
|
2202
|
+
Get a `Chunk.ChunkIterator` for more efficient traversal within the Chunk ecosystem:
|
|
2203
|
+
|
|
2204
|
+
```scala
|
|
2205
|
+
trait Chunk[+A] {
|
|
2206
|
+
def chunkIterator: Chunk.ChunkIterator[A]
|
|
2207
|
+
}
|
|
2208
|
+
```
|
|
2209
|
+
|
|
2210
|
+
`ChunkIterator` is optimized for Chunk operations and can be more efficient than the standard iterator in some cases:
|
|
2211
|
+
|
|
2212
|
+
```scala
|
|
2213
|
+
import zio.blocks.chunk.Chunk
|
|
2214
|
+
|
|
2215
|
+
val chunk = Chunk(10, 20, 30, 40)
|
|
2216
|
+
// chunk: Chunk[Int] = IndexedSeq(10, 20, 30, 40)
|
|
2217
|
+
val chunkIter = chunk.chunkIterator
|
|
2218
|
+
// chunkIter: ChunkIterator[Int] = IndexedSeq(10, 20, 30, 40)
|
|
2219
|
+
```
|
|
2220
|
+
|
|
2221
|
+
#### `Chunk#knownSize` — Get Iterator Size Hint
|
|
2222
|
+
|
|
2223
|
+
Get the known size of the chunk for iterator protocol compatibility:
|
|
2224
|
+
|
|
2225
|
+
```scala
|
|
2226
|
+
trait Chunk[+A] {
|
|
2227
|
+
def knownSize: Int
|
|
2228
|
+
}
|
|
2229
|
+
```
|
|
2230
|
+
|
|
2231
|
+
The known size helps collection builders optimize allocations:
|
|
2232
|
+
|
|
2233
|
+
```scala
|
|
2234
|
+
import zio.blocks.chunk.Chunk
|
|
2235
|
+
|
|
2236
|
+
val chunk = Chunk(1, 2, 3)
|
|
2237
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
2238
|
+
val size = chunk.knownSize
|
|
2239
|
+
// size: Int = 3
|
|
2240
|
+
```
|
|
2241
|
+
|
|
2242
|
+
### Array Operations
|
|
2243
|
+
|
|
2244
|
+
Perform bulk operations with arrays for interoperability with imperative code:
|
|
2245
|
+
|
|
2246
|
+
#### `Chunk#copyToArray` — Copy to Destination Array
|
|
2247
|
+
|
|
2248
|
+
Copy elements from the chunk to a destination array at a specified position:
|
|
2249
|
+
|
|
2250
|
+
```scala
|
|
2251
|
+
trait Chunk[+A] {
|
|
2252
|
+
def copyToArray[B >: A](dest: Array[B], destPos: Int, length: Int): Int
|
|
2253
|
+
}
|
|
2254
|
+
```
|
|
2255
|
+
|
|
2256
|
+
This is useful for efficient bulk export of chunk contents to arrays, particularly in interop scenarios. The method returns the number of elements actually copied:
|
|
2257
|
+
|
|
2258
|
+
```scala
|
|
2259
|
+
import zio.blocks.chunk.Chunk
|
|
2260
|
+
|
|
2261
|
+
val chunk = Chunk(1, 2, 3, 4, 5)
|
|
2262
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
2263
|
+
val dest = new Array[Int](10)
|
|
2264
|
+
// dest: Array[Int] = Array(0, 0, 1, 2, 3, 0, 0, 0, 0, 0)
|
|
2265
|
+
|
|
2266
|
+
val numCopied = chunk.copyToArray(dest, 2, 3)
|
|
2267
|
+
// numCopied: Int = 3
|
|
2268
|
+
dest.take(5)
|
|
2269
|
+
// res144: Array[Int] = Array(0, 0, 1, 2, 3)
|
|
2270
|
+
```
|
|
2271
|
+
|
|
2272
|
+
### Materialization and Optimization
|
|
2273
|
+
|
|
2274
|
+
When you build chunks through many concatenations, they internally form a tree structure for efficiency. However, if you plan to access elements many times, materialization converts that tree into a flat, optimized array-backed representation:
|
|
2275
|
+
|
|
2276
|
+
Force the chunk to an array-backed representation, eliminating lazy concatenation trees. Useful before performing many operations:
|
|
2277
|
+
|
|
2278
|
+
```scala
|
|
2279
|
+
trait Chunk[+A] {
|
|
2280
|
+
def materialize[A1 >: A]: Chunk[A1]
|
|
2281
|
+
}
|
|
2282
|
+
```
|
|
2283
|
+
|
|
2284
|
+
Materializing a tree-built chunk converts it to an efficient array-backed form:
|
|
2285
|
+
|
|
2286
|
+
```scala
|
|
2287
|
+
import zio.blocks.chunk.Chunk
|
|
2288
|
+
|
|
2289
|
+
// Build through many concatenations (creates Concat tree)
|
|
2290
|
+
val built = (1 to 100).foldLeft(Chunk.empty[Int]) { (acc, i) =>
|
|
2291
|
+
acc ++ Chunk(i)
|
|
2292
|
+
}
|
|
2293
|
+
// built: Chunk[Int] = IndexedSeq(
|
|
2294
|
+
// 1,
|
|
2295
|
+
// 2,
|
|
2296
|
+
// 3,
|
|
2297
|
+
// 4,
|
|
2298
|
+
// 5,
|
|
2299
|
+
// 6,
|
|
2300
|
+
// 7,
|
|
2301
|
+
// 8,
|
|
2302
|
+
// 9,
|
|
2303
|
+
// 10,
|
|
2304
|
+
// 11,
|
|
2305
|
+
// 12,
|
|
2306
|
+
// 13,
|
|
2307
|
+
// 14,
|
|
2308
|
+
// 15,
|
|
2309
|
+
// 16,
|
|
2310
|
+
// 17,
|
|
2311
|
+
// 18,
|
|
2312
|
+
// 19,
|
|
2313
|
+
// 20,
|
|
2314
|
+
// 21,
|
|
2315
|
+
// 22,
|
|
2316
|
+
// 23,
|
|
2317
|
+
// 24,
|
|
2318
|
+
// 25,
|
|
2319
|
+
// 26,
|
|
2320
|
+
// 27,
|
|
2321
|
+
// 28,
|
|
2322
|
+
// 29,
|
|
2323
|
+
// 30,
|
|
2324
|
+
// 31,
|
|
2325
|
+
// 32,
|
|
2326
|
+
// 33,
|
|
2327
|
+
// 34,
|
|
2328
|
+
// 35,
|
|
2329
|
+
// 36,
|
|
2330
|
+
// 37,
|
|
2331
|
+
// 38,
|
|
2332
|
+
// 39,
|
|
2333
|
+
// 40,
|
|
2334
|
+
// 41,
|
|
2335
|
+
// 42,
|
|
2336
|
+
// 43,
|
|
2337
|
+
// 44,
|
|
2338
|
+
// 45,
|
|
2339
|
+
// 46,
|
|
2340
|
+
// 47,
|
|
2341
|
+
// 48,
|
|
2342
|
+
// ...
|
|
2343
|
+
|
|
2344
|
+
// Materialize to array-backed representation
|
|
2345
|
+
val materialized = built.materialize
|
|
2346
|
+
// materialized: Chunk[Int] = IndexedSeq(
|
|
2347
|
+
// 1,
|
|
2348
|
+
// 2,
|
|
2349
|
+
// 3,
|
|
2350
|
+
// 4,
|
|
2351
|
+
// 5,
|
|
2352
|
+
// 6,
|
|
2353
|
+
// 7,
|
|
2354
|
+
// 8,
|
|
2355
|
+
// 9,
|
|
2356
|
+
// 10,
|
|
2357
|
+
// 11,
|
|
2358
|
+
// 12,
|
|
2359
|
+
// 13,
|
|
2360
|
+
// 14,
|
|
2361
|
+
// 15,
|
|
2362
|
+
// 16,
|
|
2363
|
+
// 17,
|
|
2364
|
+
// 18,
|
|
2365
|
+
// 19,
|
|
2366
|
+
// 20,
|
|
2367
|
+
// 21,
|
|
2368
|
+
// 22,
|
|
2369
|
+
// 23,
|
|
2370
|
+
// 24,
|
|
2371
|
+
// 25,
|
|
2372
|
+
// 26,
|
|
2373
|
+
// 27,
|
|
2374
|
+
// 28,
|
|
2375
|
+
// 29,
|
|
2376
|
+
// 30,
|
|
2377
|
+
// 31,
|
|
2378
|
+
// 32,
|
|
2379
|
+
// 33,
|
|
2380
|
+
// 34,
|
|
2381
|
+
// 35,
|
|
2382
|
+
// 36,
|
|
2383
|
+
// 37,
|
|
2384
|
+
// 38,
|
|
2385
|
+
// 39,
|
|
2386
|
+
// 40,
|
|
2387
|
+
// 41,
|
|
2388
|
+
// 42,
|
|
2389
|
+
// 43,
|
|
2390
|
+
// 44,
|
|
2391
|
+
// 45,
|
|
2392
|
+
// 46,
|
|
2393
|
+
// 47,
|
|
2394
|
+
// 48,
|
|
2395
|
+
// ...
|
|
2396
|
+
// Now faster for repeated access or further operations
|
|
2397
|
+
```
|
|
2398
|
+
|
|
2399
|
+
## Advanced Usage
|
|
2400
|
+
|
|
2401
|
+
Advanced use cases include bit-level operations, working with specialized chunk types, and comparing Chunk with other data structures:
|
|
2402
|
+
|
|
2403
|
+
### Bit Operations
|
|
2404
|
+
|
|
2405
|
+
Chunk provides specialized operations to work at the bit level with numeric types. These are useful when you need to inspect the binary representation of bytes, integers, or longs while respecting endianness preferences:
|
|
2406
|
+
|
|
2407
|
+
#### `Chunk#asBitsByte` — Convert to Byte Bits
|
|
2408
|
+
|
|
2409
|
+
Convert a chunk of bytes into a chunk of individual bits. This method is only available on `Chunk[Byte]` through an implicit constraint:
|
|
2410
|
+
|
|
2411
|
+
```scala
|
|
2412
|
+
trait Chunk[+A] {
|
|
2413
|
+
def asBitsByte(implicit ev: A <:< Byte): Chunk[Boolean]
|
|
2414
|
+
}
|
|
2415
|
+
```
|
|
2416
|
+
|
|
2417
|
+
Converting bytes to bits reveals the binary representation:
|
|
2418
|
+
|
|
2419
|
+
```scala
|
|
2420
|
+
import zio.blocks.chunk.Chunk
|
|
2421
|
+
|
|
2422
|
+
val bytes = Chunk(1.toByte, 2.toByte)
|
|
2423
|
+
// bytes: Chunk[Byte] = IndexedSeq(1, 2)
|
|
2424
|
+
val bits = bytes.asBitsByte
|
|
2425
|
+
// bits: Chunk[Boolean] = IndexedSeq(
|
|
2426
|
+
// false,
|
|
2427
|
+
// false,
|
|
2428
|
+
// false,
|
|
2429
|
+
// false,
|
|
2430
|
+
// false,
|
|
2431
|
+
// false,
|
|
2432
|
+
// false,
|
|
2433
|
+
// true,
|
|
2434
|
+
// false,
|
|
2435
|
+
// false,
|
|
2436
|
+
// false,
|
|
2437
|
+
// false,
|
|
2438
|
+
// false,
|
|
2439
|
+
// false,
|
|
2440
|
+
// true,
|
|
2441
|
+
// false
|
|
2442
|
+
// )
|
|
2443
|
+
```
|
|
2444
|
+
|
|
2445
|
+
#### `Chunk#asBitsInt` — Convert to Int Bits
|
|
2446
|
+
|
|
2447
|
+
Convert a chunk of ints into a chunk of individual bits with a specified endianness. This method is only available on `Chunk[Int]` through an implicit constraint:
|
|
2448
|
+
|
|
2449
|
+
```scala
|
|
2450
|
+
trait Chunk[+A] {
|
|
2451
|
+
def asBitsInt(endianness: Chunk.BitChunk.Endianness)(implicit ev: A <:< Int): Chunk[Boolean]
|
|
2452
|
+
}
|
|
2453
|
+
```
|
|
2454
|
+
|
|
2455
|
+
Converting integers to bits with endianness control provides bit-level access:
|
|
2456
|
+
|
|
2457
|
+
```scala
|
|
2458
|
+
import zio.blocks.chunk.Chunk
|
|
2459
|
+
|
|
2460
|
+
val ints = Chunk(1, 2, 3)
|
|
2461
|
+
// ints: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
2462
|
+
val bits = ints.asBitsInt(Chunk.BitChunk.Endianness.BigEndian)
|
|
2463
|
+
// bits: Chunk[Boolean] = IndexedSeq(
|
|
2464
|
+
// false,
|
|
2465
|
+
// false,
|
|
2466
|
+
// false,
|
|
2467
|
+
// false,
|
|
2468
|
+
// false,
|
|
2469
|
+
// false,
|
|
2470
|
+
// false,
|
|
2471
|
+
// false,
|
|
2472
|
+
// false,
|
|
2473
|
+
// false,
|
|
2474
|
+
// false,
|
|
2475
|
+
// false,
|
|
2476
|
+
// false,
|
|
2477
|
+
// false,
|
|
2478
|
+
// false,
|
|
2479
|
+
// false,
|
|
2480
|
+
// false,
|
|
2481
|
+
// false,
|
|
2482
|
+
// false,
|
|
2483
|
+
// false,
|
|
2484
|
+
// false,
|
|
2485
|
+
// false,
|
|
2486
|
+
// false,
|
|
2487
|
+
// false,
|
|
2488
|
+
// false,
|
|
2489
|
+
// false,
|
|
2490
|
+
// false,
|
|
2491
|
+
// false,
|
|
2492
|
+
// false,
|
|
2493
|
+
// false,
|
|
2494
|
+
// false,
|
|
2495
|
+
// true,
|
|
2496
|
+
// false,
|
|
2497
|
+
// false,
|
|
2498
|
+
// false,
|
|
2499
|
+
// false,
|
|
2500
|
+
// false,
|
|
2501
|
+
// false,
|
|
2502
|
+
// false,
|
|
2503
|
+
// false,
|
|
2504
|
+
// false,
|
|
2505
|
+
// false,
|
|
2506
|
+
// false,
|
|
2507
|
+
// false,
|
|
2508
|
+
// false,
|
|
2509
|
+
// false,
|
|
2510
|
+
// false,
|
|
2511
|
+
// false,
|
|
2512
|
+
// ...
|
|
2513
|
+
```
|
|
2514
|
+
|
|
2515
|
+
#### `Chunk#asBitsLong` — Convert to Long Bits
|
|
2516
|
+
|
|
2517
|
+
Convert a chunk of longs into a chunk of individual bits with a specified endianness. This method is only available on `Chunk[Long]` through an implicit constraint:
|
|
2518
|
+
|
|
2519
|
+
```scala
|
|
2520
|
+
trait Chunk[+A] {
|
|
2521
|
+
def asBitsLong(endianness: Chunk.BitChunk.Endianness)(implicit ev: A <:< Long): Chunk[Boolean]
|
|
2522
|
+
}
|
|
2523
|
+
```
|
|
2524
|
+
|
|
2525
|
+
Converting longs to bits supports both big-endian and little-endian representations:
|
|
2526
|
+
|
|
2527
|
+
```scala
|
|
2528
|
+
import zio.blocks.chunk.Chunk
|
|
2529
|
+
|
|
2530
|
+
val longs = Chunk(1L, 2L, 3L)
|
|
2531
|
+
// longs: Chunk[Long] = IndexedSeq(1L, 2L, 3L)
|
|
2532
|
+
val bits = longs.asBitsLong(Chunk.BitChunk.Endianness.LittleEndian)
|
|
2533
|
+
// bits: Chunk[Boolean] = IndexedSeq(
|
|
2534
|
+
// true,
|
|
2535
|
+
// false,
|
|
2536
|
+
// false,
|
|
2537
|
+
// false,
|
|
2538
|
+
// false,
|
|
2539
|
+
// false,
|
|
2540
|
+
// false,
|
|
2541
|
+
// false,
|
|
2542
|
+
// false,
|
|
2543
|
+
// false,
|
|
2544
|
+
// false,
|
|
2545
|
+
// false,
|
|
2546
|
+
// false,
|
|
2547
|
+
// false,
|
|
2548
|
+
// false,
|
|
2549
|
+
// false,
|
|
2550
|
+
// false,
|
|
2551
|
+
// false,
|
|
2552
|
+
// false,
|
|
2553
|
+
// false,
|
|
2554
|
+
// false,
|
|
2555
|
+
// false,
|
|
2556
|
+
// false,
|
|
2557
|
+
// false,
|
|
2558
|
+
// false,
|
|
2559
|
+
// false,
|
|
2560
|
+
// false,
|
|
2561
|
+
// false,
|
|
2562
|
+
// false,
|
|
2563
|
+
// false,
|
|
2564
|
+
// false,
|
|
2565
|
+
// false,
|
|
2566
|
+
// false,
|
|
2567
|
+
// false,
|
|
2568
|
+
// false,
|
|
2569
|
+
// false,
|
|
2570
|
+
// false,
|
|
2571
|
+
// false,
|
|
2572
|
+
// false,
|
|
2573
|
+
// false,
|
|
2574
|
+
// false,
|
|
2575
|
+
// false,
|
|
2576
|
+
// false,
|
|
2577
|
+
// false,
|
|
2578
|
+
// false,
|
|
2579
|
+
// false,
|
|
2580
|
+
// false,
|
|
2581
|
+
// false,
|
|
2582
|
+
// ...
|
|
2583
|
+
```
|
|
2584
|
+
|
|
2585
|
+
#### `Chunk#toBinaryString` — Convert Boolean Chunk to Binary String
|
|
2586
|
+
|
|
2587
|
+
Convert a chunk of booleans (representing bits) into a binary string representation:
|
|
2588
|
+
|
|
2589
|
+
```scala
|
|
2590
|
+
trait Chunk[+A] {
|
|
2591
|
+
def toBinaryString(implicit ev: A <:< Boolean): String
|
|
2592
|
+
}
|
|
2593
|
+
```
|
|
2594
|
+
|
|
2595
|
+
This operation yields a string of '0' and '1' characters corresponding to the bit values:
|
|
2596
|
+
|
|
2597
|
+
```scala
|
|
2598
|
+
import zio.blocks.chunk.Chunk
|
|
2599
|
+
|
|
2600
|
+
val bits = Chunk(true, false, true, false)
|
|
2601
|
+
```
|
|
2602
|
+
|
|
2603
|
+
The binary string shows the sequence of bits:
|
|
2604
|
+
|
|
2605
|
+
```scala
|
|
2606
|
+
bits.toBinaryString
|
|
2607
|
+
// res150: String = "1010"
|
|
2608
|
+
```
|
|
2609
|
+
|
|
2610
|
+
#### `Chunk#toPackedByte` — Pack Bits into Bytes
|
|
2611
|
+
|
|
2612
|
+
Pack a chunk of booleans (representing individual bits) into a packed byte representation:
|
|
2613
|
+
|
|
2614
|
+
```scala
|
|
2615
|
+
trait Chunk[+A] {
|
|
2616
|
+
def toPackedByte(implicit ev: A <:< Boolean): Chunk[Byte]
|
|
2617
|
+
}
|
|
2618
|
+
```
|
|
2619
|
+
|
|
2620
|
+
This is useful when you need a space-efficient representation of individual bits by packing them eight per byte. The bits are packed from left to right, with the first bit becoming the most significant bit of the first byte:
|
|
2621
|
+
|
|
2622
|
+
```scala
|
|
2623
|
+
import zio.blocks.chunk.Chunk
|
|
2624
|
+
|
|
2625
|
+
val bits = Chunk(true, false, true, false, true, false, true, false)
|
|
2626
|
+
// bits: Chunk[Boolean] = IndexedSeq(
|
|
2627
|
+
// true,
|
|
2628
|
+
// false,
|
|
2629
|
+
// true,
|
|
2630
|
+
// false,
|
|
2631
|
+
// true,
|
|
2632
|
+
// false,
|
|
2633
|
+
// true,
|
|
2634
|
+
// false
|
|
2635
|
+
// )
|
|
2636
|
+
val packed = bits.toPackedByte
|
|
2637
|
+
// packed: Chunk[Byte] = IndexedSeq(-86)
|
|
2638
|
+
```
|
|
2639
|
+
|
|
2640
|
+
#### `Chunk#toPackedInt` — Pack Bits into Integers
|
|
2641
|
+
|
|
2642
|
+
Pack a chunk of booleans into a packed integer representation with specified endianness:
|
|
2643
|
+
|
|
2644
|
+
```scala
|
|
2645
|
+
trait Chunk[+A] {
|
|
2646
|
+
def toPackedInt(endianness: Chunk.BitChunk.Endianness)(implicit ev: A <:< Boolean): Chunk[Int]
|
|
2647
|
+
}
|
|
2648
|
+
```
|
|
2649
|
+
|
|
2650
|
+
Packing bits into integers is efficient for storing large bit arrays. The endianness parameter controls how bits are ordered within each integer:
|
|
2651
|
+
|
|
2652
|
+
```scala
|
|
2653
|
+
import zio.blocks.chunk.Chunk
|
|
2654
|
+
|
|
2655
|
+
val bits = Chunk(true, false, true, false, true, false, true, false)
|
|
2656
|
+
// bits: Chunk[Boolean] = IndexedSeq(
|
|
2657
|
+
// true,
|
|
2658
|
+
// false,
|
|
2659
|
+
// true,
|
|
2660
|
+
// false,
|
|
2661
|
+
// true,
|
|
2662
|
+
// false,
|
|
2663
|
+
// true,
|
|
2664
|
+
// false
|
|
2665
|
+
// )
|
|
2666
|
+
val packed = bits.toPackedInt(Chunk.BitChunk.Endianness.BigEndian)
|
|
2667
|
+
// packed: Chunk[Int] = IndexedSeq(170)
|
|
2668
|
+
```
|
|
2669
|
+
|
|
2670
|
+
#### `Chunk#toPackedLong` — Pack Bits into Longs
|
|
2671
|
+
|
|
2672
|
+
Pack a chunk of booleans into a packed long representation with specified endianness:
|
|
2673
|
+
|
|
2674
|
+
```scala
|
|
2675
|
+
trait Chunk[+A] {
|
|
2676
|
+
def toPackedLong(endianness: Chunk.BitChunk.Endianness)(implicit ev: A <:< Boolean): Chunk[Long]
|
|
2677
|
+
}
|
|
2678
|
+
```
|
|
2679
|
+
|
|
2680
|
+
Packing bits into longs is the most space-efficient approach for bit storage, packing 64 bits per long. Choose the endianness that matches your serialization format:
|
|
2681
|
+
|
|
2682
|
+
```scala
|
|
2683
|
+
import zio.blocks.chunk.Chunk
|
|
2684
|
+
|
|
2685
|
+
val bits = Chunk(true, false, true, false, true, false, true, false)
|
|
2686
|
+
// bits: Chunk[Boolean] = IndexedSeq(
|
|
2687
|
+
// true,
|
|
2688
|
+
// false,
|
|
2689
|
+
// true,
|
|
2690
|
+
// false,
|
|
2691
|
+
// true,
|
|
2692
|
+
// false,
|
|
2693
|
+
// true,
|
|
2694
|
+
// false
|
|
2695
|
+
// )
|
|
2696
|
+
val packed = bits.toPackedLong(Chunk.BitChunk.Endianness.LittleEndian)
|
|
2697
|
+
// packed: Chunk[Long] = IndexedSeq(6124895493223874560L)
|
|
2698
|
+
```
|
|
2699
|
+
|
|
2700
|
+
#### `Chunk#&` — Bitwise AND
|
|
2701
|
+
|
|
2702
|
+
Perform bitwise AND between two boolean chunks:
|
|
2703
|
+
|
|
2704
|
+
```scala
|
|
2705
|
+
trait Chunk[+A] {
|
|
2706
|
+
def &(that: Chunk[Boolean])(implicit ev: A <:< Boolean): Chunk.BitChunkByte
|
|
2707
|
+
}
|
|
2708
|
+
```
|
|
2709
|
+
|
|
2710
|
+
Bitwise AND operates on boolean chunks, returning a `BitChunkByte` representing the packed result:
|
|
2711
|
+
|
|
2712
|
+
```scala
|
|
2713
|
+
import zio.blocks.chunk.Chunk
|
|
2714
|
+
|
|
2715
|
+
val bits1 = Chunk(true, true, false, false)
|
|
2716
|
+
// bits1: Chunk[Boolean] = IndexedSeq(true, true, false, false)
|
|
2717
|
+
val bits2 = Chunk(true, false, true, false)
|
|
2718
|
+
// bits2: Chunk[Boolean] = IndexedSeq(true, false, true, false)
|
|
2719
|
+
val result = bits1 & bits2
|
|
2720
|
+
// result: BitChunkByte = IndexedSeq(true, false, false, false)
|
|
2721
|
+
```
|
|
2722
|
+
|
|
2723
|
+
#### `Chunk#|` — Bitwise OR
|
|
2724
|
+
|
|
2725
|
+
Perform bitwise OR between two boolean chunks:
|
|
2726
|
+
|
|
2727
|
+
```scala
|
|
2728
|
+
trait Chunk[+A] {
|
|
2729
|
+
def |(that: Chunk[Boolean])(implicit ev: A <:< Boolean): Chunk.BitChunkByte
|
|
2730
|
+
}
|
|
2731
|
+
```
|
|
2732
|
+
|
|
2733
|
+
Bitwise OR combines boolean chunks with an OR operation:
|
|
2734
|
+
|
|
2735
|
+
```scala
|
|
2736
|
+
import zio.blocks.chunk.Chunk
|
|
2737
|
+
|
|
2738
|
+
val bits1 = Chunk(true, false, false, false)
|
|
2739
|
+
// bits1: Chunk[Boolean] = IndexedSeq(true, false, false, false)
|
|
2740
|
+
val bits2 = Chunk(false, true, false, false)
|
|
2741
|
+
// bits2: Chunk[Boolean] = IndexedSeq(false, true, false, false)
|
|
2742
|
+
val result = bits1 | bits2
|
|
2743
|
+
// result: BitChunkByte = IndexedSeq(true, true, false, false)
|
|
2744
|
+
```
|
|
2745
|
+
|
|
2746
|
+
#### `Chunk#^` — Bitwise XOR
|
|
2747
|
+
|
|
2748
|
+
Perform bitwise XOR between two boolean chunks:
|
|
2749
|
+
|
|
2750
|
+
```scala
|
|
2751
|
+
trait Chunk[+A] {
|
|
2752
|
+
def ^(that: Chunk[Boolean])(implicit ev: A <:< Boolean): Chunk.BitChunkByte
|
|
2753
|
+
}
|
|
2754
|
+
```
|
|
2755
|
+
|
|
2756
|
+
Bitwise XOR returns true when bits differ:
|
|
2757
|
+
|
|
2758
|
+
```scala
|
|
2759
|
+
import zio.blocks.chunk.Chunk
|
|
2760
|
+
|
|
2761
|
+
val bits1 = Chunk(true, false, true, false)
|
|
2762
|
+
// bits1: Chunk[Boolean] = IndexedSeq(true, false, true, false)
|
|
2763
|
+
val bits2 = Chunk(true, true, false, false)
|
|
2764
|
+
// bits2: Chunk[Boolean] = IndexedSeq(true, true, false, false)
|
|
2765
|
+
val result = bits1 ^ bits2
|
|
2766
|
+
// result: BitChunkByte = IndexedSeq(false, true, true, false)
|
|
2767
|
+
```
|
|
2768
|
+
|
|
2769
|
+
#### `Chunk#negate` — Bitwise NOT
|
|
2770
|
+
|
|
2771
|
+
Perform bitwise NOT (negation) on a boolean chunk:
|
|
2772
|
+
|
|
2773
|
+
```scala
|
|
2774
|
+
trait Chunk[+A] {
|
|
2775
|
+
def negate(implicit ev: A <:< Boolean): Chunk.BitChunkByte
|
|
2776
|
+
}
|
|
2777
|
+
```
|
|
2778
|
+
|
|
2779
|
+
Bitwise NOT inverts all bits in the chunk:
|
|
2780
|
+
|
|
2781
|
+
```scala
|
|
2782
|
+
import zio.blocks.chunk.Chunk
|
|
2783
|
+
|
|
2784
|
+
val bits = Chunk(true, false, true, false)
|
|
2785
|
+
// bits: Chunk[Boolean] = IndexedSeq(true, false, true, false)
|
|
2786
|
+
val inverted = bits.negate
|
|
2787
|
+
// inverted: BitChunkByte = IndexedSeq(false, true, false, true)
|
|
2788
|
+
```
|
|
2789
|
+
|
|
2790
|
+
### Text Operations
|
|
2791
|
+
|
|
2792
|
+
Chunk makes it convenient to work with text by converting byte or character chunks into readable strings. You can also encode chunks as Base64 for safe transmission or storage in text-based formats:
|
|
2793
|
+
|
|
2794
|
+
#### `Chunk#asString` — Convert to String
|
|
2795
|
+
|
|
2796
|
+
Convert a chunk of bytes or characters into a string. Supports `Chunk[Byte]`, `Chunk[Char]`, and `Chunk[String]`:
|
|
2797
|
+
|
|
2798
|
+
```scala
|
|
2799
|
+
import zio.blocks.chunk.Chunk
|
|
2800
|
+
|
|
2801
|
+
val chars = Chunk('H', 'i')
|
|
2802
|
+
val bytes = Chunk(72.toByte, 105.toByte)
|
|
2803
|
+
```
|
|
2804
|
+
|
|
2805
|
+
Converting byte and character chunks to strings:
|
|
2806
|
+
|
|
2807
|
+
```scala
|
|
2808
|
+
val str = chars.asString
|
|
2809
|
+
// str: String = "Hi"
|
|
2810
|
+
val strFromBytes = bytes.asString
|
|
2811
|
+
// strFromBytes: String = "Hi"
|
|
2812
|
+
```
|
|
2813
|
+
|
|
2814
|
+
#### `Chunk#asString(charset)` — Convert to String with Charset
|
|
2815
|
+
|
|
2816
|
+
Convert a chunk of bytes into a string using a specified character encoding:
|
|
2817
|
+
|
|
2818
|
+
```scala
|
|
2819
|
+
trait Chunk[+A] {
|
|
2820
|
+
def asString(charset: Charset)(implicit ev: A <:< Byte): String
|
|
2821
|
+
}
|
|
2822
|
+
```
|
|
2823
|
+
|
|
2824
|
+
When working with byte chunks from external sources, you may need to specify the character encoding. Use UTF-8 for most cases, but other encodings are available:
|
|
2825
|
+
|
|
2826
|
+
```scala
|
|
2827
|
+
import zio.blocks.chunk.Chunk
|
|
2828
|
+
import java.nio.charset.StandardCharsets
|
|
2829
|
+
|
|
2830
|
+
val utf8Bytes = Chunk(72.toByte, 101.toByte, 108.toByte, 108.toByte, 111.toByte)
|
|
2831
|
+
// utf8Bytes: Chunk[Byte] = IndexedSeq(72, 101, 108, 108, 111)
|
|
2832
|
+
val stringUTF8 = utf8Bytes.asString(StandardCharsets.UTF_8)
|
|
2833
|
+
// stringUTF8: String = "Hello"
|
|
2834
|
+
```
|
|
2835
|
+
|
|
2836
|
+
#### `Chunk#asBase64String` — Encode as Base64
|
|
2837
|
+
|
|
2838
|
+
Convert a chunk of bytes or characters to a Base64-encoded string:
|
|
2839
|
+
|
|
2840
|
+
```scala
|
|
2841
|
+
import zio.blocks.chunk.Chunk
|
|
2842
|
+
|
|
2843
|
+
val bytes = Chunk(1.toByte, 2.toByte, 3.toByte)
|
|
2844
|
+
// bytes: Chunk[Byte] = IndexedSeq(1, 2, 3)
|
|
2845
|
+
val base64 = bytes.asBase64String
|
|
2846
|
+
// base64: String = "AQID"
|
|
2847
|
+
```
|
|
2848
|
+
|
|
2849
|
+
### Advanced Transformations
|
|
2850
|
+
|
|
2851
|
+
Chunk supports powerful transformation patterns for complex use cases. Collect elements while a condition holds, partition results into success and failure paths using Either, and attach indices to track element positions:
|
|
2852
|
+
|
|
2853
|
+
#### `Chunk#collectWhile` — Collect with Early Exit
|
|
2854
|
+
|
|
2855
|
+
Apply a partial function while a condition holds, stopping at the first non-match:
|
|
2856
|
+
|
|
2857
|
+
```scala
|
|
2858
|
+
import zio.blocks.chunk.Chunk
|
|
2859
|
+
|
|
2860
|
+
val values: Chunk[Any] = Chunk(1, 2, "x", 3, 4)
|
|
2861
|
+
// values: Chunk[Any] = IndexedSeq(1, 2, "x", 3, 4)
|
|
2862
|
+
val collected = values.collectWhile { case n: Int => n * 10 }
|
|
2863
|
+
// collected: Chunk[Int] = IndexedSeq(10, 20)
|
|
2864
|
+
```
|
|
2865
|
+
|
|
2866
|
+
#### `Chunk#partitionMap` — Partition into Two Chunks by Either Result
|
|
2867
|
+
|
|
2868
|
+
Partition elements by applying a total function that returns `Either`:
|
|
2869
|
+
|
|
2870
|
+
```scala
|
|
2871
|
+
trait Chunk[+A] {
|
|
2872
|
+
def partitionMap[B, C](f: A => Either[B, C]): (Chunk[B], Chunk[C])
|
|
2873
|
+
}
|
|
2874
|
+
```
|
|
2875
|
+
|
|
2876
|
+
Partitioning elements by an Either result separates success and failure cases:
|
|
2877
|
+
|
|
2878
|
+
```scala
|
|
2879
|
+
import zio.blocks.chunk.Chunk
|
|
2880
|
+
|
|
2881
|
+
val numbers = Chunk(1, 2, 3, 4, 5)
|
|
2882
|
+
// numbers: Chunk[Int] = IndexedSeq(1, 2, 3, 4, 5)
|
|
2883
|
+
val (evens, odds) = numbers.partitionMap { n =>
|
|
2884
|
+
if (n % 2 == 0) Left(n) else Right(n)
|
|
2885
|
+
}
|
|
2886
|
+
// evens: Chunk[Int] = IndexedSeq(2, 4)
|
|
2887
|
+
// odds: Chunk[Int] = IndexedSeq(1, 3, 5)
|
|
2888
|
+
```
|
|
2889
|
+
|
|
2890
|
+
#### `Chunk#zipWithIndexFrom` — Zip with Index Starting at Custom Value
|
|
2891
|
+
|
|
2892
|
+
Zip elements with indices starting from a custom value:
|
|
2893
|
+
|
|
2894
|
+
```scala
|
|
2895
|
+
import zio.blocks.chunk.Chunk
|
|
2896
|
+
|
|
2897
|
+
val chunk = Chunk("a", "b", "c")
|
|
2898
|
+
```
|
|
2899
|
+
|
|
2900
|
+
Zipping with a custom starting index pairs each element:
|
|
2901
|
+
|
|
2902
|
+
```scala
|
|
2903
|
+
chunk.zipWithIndexFrom(10)
|
|
2904
|
+
// res164: Chunk[Tuple2[String, Int]] = IndexedSeq(
|
|
2905
|
+
// ("a", 10),
|
|
2906
|
+
// ("b", 11),
|
|
2907
|
+
// ("c", 12)
|
|
2908
|
+
// )
|
|
2909
|
+
```
|
|
2910
|
+
|
|
2911
|
+
### Safe Operations
|
|
2912
|
+
|
|
2913
|
+
Chunks can be empty, and these operations help you handle that case gracefully. Rather than throwing exceptions or returning null, these methods provide type-safe fallbacks and alternatives when working with potentially empty chunks:
|
|
2914
|
+
|
|
2915
|
+
Return the chunk if non-empty, otherwise return an alternative:
|
|
2916
|
+
|
|
2917
|
+
```scala
|
|
2918
|
+
trait Chunk[+A] {
|
|
2919
|
+
def nonEmptyOrElse[B](ifEmpty: => B)(fn: NonEmptyChunk[A] => B): B
|
|
2920
|
+
}
|
|
2921
|
+
```
|
|
2922
|
+
|
|
2923
|
+
Safely handling empty and non-empty cases provides type-safe alternatives:
|
|
2924
|
+
|
|
2925
|
+
```scala
|
|
2926
|
+
import zio.blocks.chunk.{Chunk, NonEmptyChunk}
|
|
2927
|
+
|
|
2928
|
+
val chunk = Chunk(1, 2, 3)
|
|
2929
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
2930
|
+
val nonEmpty = NonEmptyChunk(1, 2, 3)
|
|
2931
|
+
// nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
|
|
2932
|
+
val result = nonEmpty.nonEmptyOrElse(Chunk.empty[Int])(identity)
|
|
2933
|
+
// result: Chunk[Int] | NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
|
|
2934
|
+
|
|
2935
|
+
val empty = Chunk.empty[Int]
|
|
2936
|
+
// empty: Chunk[Int] = IndexedSeq()
|
|
2937
|
+
val result2 = empty match {
|
|
2938
|
+
case c if c.isEmpty => Chunk(99)
|
|
2939
|
+
case c => c
|
|
2940
|
+
}
|
|
2941
|
+
// result2: Chunk[Int] = IndexedSeq(99)
|
|
2942
|
+
```
|
|
2943
|
+
|
|
2944
|
+
## NonEmptyChunk
|
|
2945
|
+
|
|
2946
|
+
`NonEmptyChunk[A]` is a **type-safe wrapper** around `Chunk[A]` that **guarantees the chunk is non-empty**. It provides the same operations as `Chunk` but with methods like `head` and `last` returning `A` directly instead of `Option[A]` or throwing an exception. This eliminates the need for runtime checks on common operations and makes empty-case handling explicit at the type level.
|
|
2947
|
+
|
|
2948
|
+
`NonEmptyChunk[A]`:
|
|
2949
|
+
- Is a purely functional, immutable sequence of at least one element
|
|
2950
|
+
- Provides type-safe access to first and last elements
|
|
2951
|
+
- Supports all Chunk operations while maintaining the non-empty guarantee
|
|
2952
|
+
- Allows safe reduction operations without requiring a default value
|
|
2953
|
+
- Integrates seamlessly with Chunk for interoperability
|
|
2954
|
+
|
|
2955
|
+
### Construction
|
|
2956
|
+
|
|
2957
|
+
Create a `NonEmptyChunk` directly using varargs syntax:
|
|
2958
|
+
|
|
2959
|
+
```scala
|
|
2960
|
+
object NonEmptyChunk {
|
|
2961
|
+
def apply[A](a: A, as: A*): NonEmptyChunk[A]
|
|
2962
|
+
}
|
|
2963
|
+
```
|
|
2964
|
+
|
|
2965
|
+
Creating a non-empty chunk with direct constructor:
|
|
2966
|
+
|
|
2967
|
+
```scala
|
|
2968
|
+
import zio.blocks.chunk.NonEmptyChunk
|
|
2969
|
+
|
|
2970
|
+
val nonEmpty = NonEmptyChunk(1, 2, 3)
|
|
2971
|
+
// nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
|
|
2972
|
+
```
|
|
2973
|
+
|
|
2974
|
+
Create a `NonEmptyChunk` from an existing `Chunk` using `fromChunk`, which returns `Option[NonEmptyChunk[A]]`:
|
|
2975
|
+
|
|
2976
|
+
```scala
|
|
2977
|
+
import zio.blocks.chunk.{Chunk, NonEmptyChunk}
|
|
2978
|
+
|
|
2979
|
+
val chunk = Chunk(1, 2, 3)
|
|
2980
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
2981
|
+
val maybeNonEmpty: Option[NonEmptyChunk[Int]] = NonEmptyChunk.fromChunk(chunk)
|
|
2982
|
+
// maybeNonEmpty: Option[NonEmptyChunk[Int]] = Some(NonEmptyChunk(1, 2, 3))
|
|
2983
|
+
|
|
2984
|
+
val empty = Chunk.empty[Int]
|
|
2985
|
+
// empty: Chunk[Int] = IndexedSeq()
|
|
2986
|
+
val nothingHere: Option[NonEmptyChunk[Int]] = NonEmptyChunk.fromChunk(empty)
|
|
2987
|
+
// nothingHere: Option[NonEmptyChunk[Int]] = None
|
|
2988
|
+
```
|
|
2989
|
+
|
|
2990
|
+
Create from a Scala cons list using `fromCons` (requires a non-empty cons list):
|
|
2991
|
+
|
|
2992
|
+
```scala
|
|
2993
|
+
import zio.blocks.chunk.NonEmptyChunk
|
|
2994
|
+
|
|
2995
|
+
val list = 1 :: 2 :: 3 :: Nil
|
|
2996
|
+
// list: List[Int] = List(1, 2, 3)
|
|
2997
|
+
val nonEmpty = list match {
|
|
2998
|
+
case cons: scala.collection.immutable.::[Int] => NonEmptyChunk.fromCons(cons)
|
|
2999
|
+
case _ => throw new IllegalArgumentException("list must be non-empty")
|
|
3000
|
+
}
|
|
3001
|
+
// nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
|
|
3002
|
+
```
|
|
3003
|
+
|
|
3004
|
+
Create from an iterable with at least one guaranteed element using `fromIterable`:
|
|
3005
|
+
|
|
3006
|
+
```scala
|
|
3007
|
+
import zio.blocks.chunk.NonEmptyChunk
|
|
3008
|
+
|
|
3009
|
+
val nonEmpty = NonEmptyChunk.fromIterable(5, List(1, 2, 3, 4))
|
|
3010
|
+
// nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(5, 1, 2, 3, 4)
|
|
3011
|
+
```
|
|
3012
|
+
|
|
3013
|
+
### Safe Access to Endpoints
|
|
3014
|
+
|
|
3015
|
+
Access the first and last elements without risk of exception:
|
|
3016
|
+
|
|
3017
|
+
```scala
|
|
3018
|
+
trait NonEmptyChunk[+A] {
|
|
3019
|
+
def head: A
|
|
3020
|
+
def last: A
|
|
3021
|
+
}
|
|
3022
|
+
```
|
|
3023
|
+
|
|
3024
|
+
Safely access endpoints of a guaranteed non-empty chunk:
|
|
3025
|
+
|
|
3026
|
+
```scala
|
|
3027
|
+
import zio.blocks.chunk.NonEmptyChunk
|
|
3028
|
+
|
|
3029
|
+
val chunk = NonEmptyChunk(10, 20, 30, 40)
|
|
3030
|
+
// chunk: NonEmptyChunk[Int] = NonEmptyChunk(10, 20, 30, 40)
|
|
3031
|
+
|
|
3032
|
+
chunk.head
|
|
3033
|
+
// res171: Int = 10
|
|
3034
|
+
chunk.last
|
|
3035
|
+
// res172: Int = 40
|
|
3036
|
+
chunk.size
|
|
3037
|
+
// res173: Int = 4
|
|
3038
|
+
```
|
|
3039
|
+
|
|
3040
|
+
### Reduction and Aggregation
|
|
3041
|
+
|
|
3042
|
+
Reduce a non-empty chunk without requiring a starting value, since the chunk is guaranteed to have at least one element:
|
|
3043
|
+
|
|
3044
|
+
```scala
|
|
3045
|
+
trait NonEmptyChunk[+A] {
|
|
3046
|
+
def reduce[B >: A](op: (B, B) => B): B
|
|
3047
|
+
def reduceMapLeft[B](map: A => B)(reduce: (B, A) => B): B
|
|
3048
|
+
def reduceMapRight[B](map: A => B)(reduce: (A, B) => B): B
|
|
3049
|
+
}
|
|
3050
|
+
```
|
|
3051
|
+
|
|
3052
|
+
Reduction operations always produce a value (no Optional needed):
|
|
3053
|
+
|
|
3054
|
+
```scala
|
|
3055
|
+
import zio.blocks.chunk.NonEmptyChunk
|
|
3056
|
+
|
|
3057
|
+
val numbers = NonEmptyChunk(1, 2, 3, 4, 5)
|
|
3058
|
+
// numbers: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4, 5)
|
|
3059
|
+
|
|
3060
|
+
val sum = numbers.reduce(_ + _)
|
|
3061
|
+
// sum: Int = 15
|
|
3062
|
+
|
|
3063
|
+
val product = numbers.reduceMapLeft[Int](identity)(_ * _)
|
|
3064
|
+
// product: Int = 120
|
|
3065
|
+
```
|
|
3066
|
+
|
|
3067
|
+
### Transformations
|
|
3068
|
+
|
|
3069
|
+
Map and transform elements while preserving the non-empty guarantee:
|
|
3070
|
+
|
|
3071
|
+
```scala
|
|
3072
|
+
trait NonEmptyChunk[+A] {
|
|
3073
|
+
def map[B](f: A => B): NonEmptyChunk[B]
|
|
3074
|
+
def flatMap[B](f: A => NonEmptyChunk[B]): NonEmptyChunk[B]
|
|
3075
|
+
def flatten[B](implicit ev: A <:< NonEmptyChunk[B]): NonEmptyChunk[B]
|
|
3076
|
+
def sorted[B >: A](implicit ord: Ordering[B]): NonEmptyChunk[B]
|
|
3077
|
+
def sortBy[B](f: A => B)(implicit ord: Ordering[B]): NonEmptyChunk[A]
|
|
3078
|
+
def distinct: NonEmptyChunk[A]
|
|
3079
|
+
def reverse: NonEmptyChunk[A]
|
|
3080
|
+
}
|
|
3081
|
+
```
|
|
3082
|
+
|
|
3083
|
+
Transformations maintain the non-empty property for map, flatMap, and other structure-preserving operations:
|
|
3084
|
+
|
|
3085
|
+
```scala
|
|
3086
|
+
import zio.blocks.chunk.NonEmptyChunk
|
|
3087
|
+
|
|
3088
|
+
val numbers = NonEmptyChunk(3, 1, 4, 1, 5)
|
|
3089
|
+
// numbers: NonEmptyChunk[Int] = NonEmptyChunk(3, 1, 4, 1, 5)
|
|
3090
|
+
|
|
3091
|
+
val doubled = numbers.map(_ * 2)
|
|
3092
|
+
// doubled: NonEmptyChunk[Int] = NonEmptyChunk(6, 2, 8, 2, 10)
|
|
3093
|
+
|
|
3094
|
+
val sorted = numbers.sorted
|
|
3095
|
+
// sorted: NonEmptyChunk[Int] = NonEmptyChunk(1, 1, 3, 4, 5)
|
|
3096
|
+
```
|
|
3097
|
+
|
|
3098
|
+
### Grouping and Aggregation
|
|
3099
|
+
|
|
3100
|
+
Group elements while maintaining non-empty chunks in each group:
|
|
3101
|
+
|
|
3102
|
+
```scala
|
|
3103
|
+
trait NonEmptyChunk[+A] {
|
|
3104
|
+
def groupBy[K](f: A => K): Map[K, NonEmptyChunk[A]]
|
|
3105
|
+
def groupMap[K, V](key: A => K)(f: A => V): Map[K, NonEmptyChunk[V]]
|
|
3106
|
+
def grouped(size: Int): Iterator[NonEmptyChunk[A]]
|
|
3107
|
+
}
|
|
3108
|
+
```
|
|
3109
|
+
|
|
3110
|
+
Grouping operations guarantee non-empty result chunks:
|
|
3111
|
+
|
|
3112
|
+
```scala
|
|
3113
|
+
import zio.blocks.chunk.NonEmptyChunk
|
|
3114
|
+
|
|
3115
|
+
case class Person(name: String, age: Int)
|
|
3116
|
+
val people = NonEmptyChunk(Person("Alice", 30), Person("Bob", 25), Person("Carol", 30))
|
|
3117
|
+
// people: NonEmptyChunk[Person] = NonEmptyChunk(Person(Alice,30), Person(Bob,25), Person(Carol,30))
|
|
3118
|
+
|
|
3119
|
+
people.groupBy(_.age)
|
|
3120
|
+
// res177: Map[Int, NonEmptyChunk[Person]] = Map(
|
|
3121
|
+
// 25 -> NonEmptyChunk(Person(Bob,25)),
|
|
3122
|
+
// 30 -> NonEmptyChunk(Person(Alice,30), Person(Carol,30))
|
|
3123
|
+
// )
|
|
3124
|
+
```
|
|
3125
|
+
|
|
3126
|
+
### Combining and Concatenating
|
|
3127
|
+
|
|
3128
|
+
Extend a non-empty chunk with additional elements or other chunks:
|
|
3129
|
+
|
|
3130
|
+
```scala
|
|
3131
|
+
trait NonEmptyChunk[+A] {
|
|
3132
|
+
def appended[A1 >: A](a: A1): NonEmptyChunk[A1]
|
|
3133
|
+
def prepended[A1 >: A](a: A1): NonEmptyChunk[A1]
|
|
3134
|
+
def ++[A1 >: A](that: Chunk[A1]): NonEmptyChunk[A1]
|
|
3135
|
+
def :+[A1 >: A](a: A1): NonEmptyChunk[A1] // alias for appended
|
|
3136
|
+
def +:[A1 >: A](a: A1): NonEmptyChunk[A1] // alias for prepended
|
|
3137
|
+
}
|
|
3138
|
+
```
|
|
3139
|
+
|
|
3140
|
+
Concatenation operations preserve the non-empty guarantee:
|
|
3141
|
+
|
|
3142
|
+
```scala
|
|
3143
|
+
import zio.blocks.chunk.{Chunk, NonEmptyChunk}
|
|
3144
|
+
|
|
3145
|
+
val nonEmpty = NonEmptyChunk(1, 2, 3)
|
|
3146
|
+
// nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
|
|
3147
|
+
val chunk = Chunk(4, 5, 6)
|
|
3148
|
+
// chunk: Chunk[Int] = IndexedSeq(4, 5, 6)
|
|
3149
|
+
|
|
3150
|
+
val appended = nonEmpty :+ 4
|
|
3151
|
+
// appended: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4)
|
|
3152
|
+
|
|
3153
|
+
val concatenated = nonEmpty ++ chunk
|
|
3154
|
+
// concatenated: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4, 5, 6)
|
|
3155
|
+
|
|
3156
|
+
val prepended = 0 +: nonEmpty
|
|
3157
|
+
// prepended: NonEmptyChunk[Int] = NonEmptyChunk(0, 1, 2, 3)
|
|
3158
|
+
```
|
|
3159
|
+
|
|
3160
|
+
### Conversion to Chunk
|
|
3161
|
+
|
|
3162
|
+
Convert a `NonEmptyChunk` back to a regular `Chunk`, losing the non-empty guarantee but gaining compatibility with Chunk APIs:
|
|
3163
|
+
|
|
3164
|
+
```scala
|
|
3165
|
+
trait NonEmptyChunk[+A] {
|
|
3166
|
+
def toChunk: Chunk[A]
|
|
3167
|
+
}
|
|
3168
|
+
```
|
|
3169
|
+
|
|
3170
|
+
Converting back to a `Chunk` for use with generic Chunk operations:
|
|
3171
|
+
|
|
3172
|
+
```scala
|
|
3173
|
+
import zio.blocks.chunk.{Chunk, NonEmptyChunk}
|
|
3174
|
+
|
|
3175
|
+
val nonEmpty = NonEmptyChunk(1, 2, 3)
|
|
3176
|
+
// nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
|
|
3177
|
+
val chunk: Chunk[Int] = nonEmpty.toChunk
|
|
3178
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
3179
|
+
|
|
3180
|
+
// Now you can use Chunk operations that return Chunk[A] instead of NonEmptyChunk[A]
|
|
3181
|
+
val filtered: Chunk[Int] = chunk.filter(_ > 1)
|
|
3182
|
+
// filtered: Chunk[Int] = IndexedSeq(2, 3)
|
|
3183
|
+
```
|
|
3184
|
+
|
|
3185
|
+
### Conversion to Scala Cons List
|
|
3186
|
+
|
|
3187
|
+
Convert a `NonEmptyChunk` to a Scala cons list (`::[A]`), maintaining the non-empty guarantee:
|
|
3188
|
+
|
|
3189
|
+
```scala
|
|
3190
|
+
trait NonEmptyChunk[+A] {
|
|
3191
|
+
def toCons[A1 >: A]: ::[A1]
|
|
3192
|
+
}
|
|
3193
|
+
```
|
|
3194
|
+
|
|
3195
|
+
Since `NonEmptyChunk` is guaranteed to be non-empty, conversion to Scala's cons list is safe and always returns a `::` (non-empty list) rather than `Nil`:
|
|
3196
|
+
|
|
3197
|
+
```scala
|
|
3198
|
+
import zio.blocks.chunk.NonEmptyChunk
|
|
3199
|
+
|
|
3200
|
+
val nonEmpty = NonEmptyChunk(1, 2, 3)
|
|
3201
|
+
// nonEmpty: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3)
|
|
3202
|
+
val consList: scala.collection.immutable.::[Int] = nonEmpty.toCons
|
|
3203
|
+
// consList: ::[Int] = List(1, 2, 3)
|
|
3204
|
+
|
|
3205
|
+
// Access head and tail like a normal cons cell
|
|
3206
|
+
consList.head
|
|
3207
|
+
// res181: Int = 1
|
|
3208
|
+
consList.tail
|
|
3209
|
+
// res182: List[Int] = List(2, 3)
|
|
3210
|
+
```
|
|
3211
|
+
|
|
3212
|
+
**Use case:** Interoperability with code expecting Scala cons lists; recursive list processing that relies on the structure being non-empty.
|
|
3213
|
+
|
|
3214
|
+
### Integration with Chunk Operations
|
|
3215
|
+
|
|
3216
|
+
Many Chunk operations are available on `NonEmptyChunk` and return `NonEmptyChunk` when the structure is guaranteed to remain non-empty:
|
|
3217
|
+
|
|
3218
|
+
```scala
|
|
3219
|
+
import zio.blocks.chunk.{Chunk, NonEmptyChunk}
|
|
3220
|
+
|
|
3221
|
+
val chunk = NonEmptyChunk(1, 2, 3, 4, 5)
|
|
3222
|
+
// chunk: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4, 5)
|
|
3223
|
+
|
|
3224
|
+
// These return NonEmptyChunk[A]
|
|
3225
|
+
val mapped = chunk.map(_ * 2)
|
|
3226
|
+
// mapped: NonEmptyChunk[Int] = NonEmptyChunk(2, 4, 6, 8, 10)
|
|
3227
|
+
|
|
3228
|
+
val sorted = chunk.sorted
|
|
3229
|
+
// sorted: NonEmptyChunk[Int] = NonEmptyChunk(1, 2, 3, 4, 5)
|
|
3230
|
+
|
|
3231
|
+
// These return Chunk[A] (since size might change)
|
|
3232
|
+
val filtered = chunk.toChunk.filter(_ > 2)
|
|
3233
|
+
// filtered: Chunk[Int] = IndexedSeq(3, 4, 5)
|
|
3234
|
+
```
|
|
3235
|
+
|
|
3236
|
+
## ChunkMap
|
|
3237
|
+
|
|
3238
|
+
`ChunkMap[K, V]` is an order-preserving immutable map backed by parallel chunks. It maintains insertion order during iteration:
|
|
3239
|
+
|
|
3240
|
+
The type defines the following members:
|
|
3241
|
+
|
|
3242
|
+
```scala
|
|
3243
|
+
object ChunkMap {
|
|
3244
|
+
def empty[K, V]: ChunkMap[K, V]
|
|
3245
|
+
def apply[K, V](elems: (K, V)*): ChunkMap[K, V]
|
|
3246
|
+
def fromChunk[K, V](chunk: Chunk[(K, V)]): ChunkMap[K, V]
|
|
3247
|
+
def fromChunks[K, V](keys: Chunk[K], values: Chunk[V]): ChunkMap[K, V]
|
|
3248
|
+
def newBuilder[K, V]: Builder[(K, V), ChunkMap[K, V]]
|
|
3249
|
+
class Indexed[K, V](val underlying: ChunkMap[K, V]) { ... }
|
|
3250
|
+
}
|
|
3251
|
+
```
|
|
3252
|
+
|
|
3253
|
+
We can create a simple map using the `apply` method and then perform basic operations like retrieving a value by key, updating a key with a new value, or removing a key:
|
|
3254
|
+
|
|
3255
|
+
```scala
|
|
3256
|
+
import zio.blocks.chunk.{Chunk, ChunkMap}
|
|
3257
|
+
|
|
3258
|
+
val map = ChunkMap("a" -> 1, "b" -> 2, "c" -> 3)
|
|
3259
|
+
// map: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3)
|
|
3260
|
+
map.get("b")
|
|
3261
|
+
// res185: Option[Int] = Some(2)
|
|
3262
|
+
map.updated("d", 4)
|
|
3263
|
+
// res186: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 4)
|
|
3264
|
+
map.removed("b")
|
|
3265
|
+
// res187: ChunkMap[String, Int] = Map("a" -> 1, "c" -> 3)
|
|
3266
|
+
```
|
|
3267
|
+
|
|
3268
|
+
### Creating ChunkMap
|
|
3269
|
+
|
|
3270
|
+
`ChunkMap` provides several factory methods for construction from different sources:
|
|
3271
|
+
|
|
3272
|
+
The companion object defines these construction methods:
|
|
3273
|
+
|
|
3274
|
+
```scala
|
|
3275
|
+
object ChunkMap {
|
|
3276
|
+
def empty[K, V]: ChunkMap[K, V]
|
|
3277
|
+
def apply[K, V](elems: (K, V)*): ChunkMap[K, V]
|
|
3278
|
+
def fromChunk[K, V](chunk: Chunk[(K, V)]): ChunkMap[K, V]
|
|
3279
|
+
def fromChunks[K, V](keys: Chunk[K], values: Chunk[V]): ChunkMap[K, V]
|
|
3280
|
+
}
|
|
3281
|
+
```
|
|
3282
|
+
|
|
3283
|
+
Create an empty map for a specific key and value type:
|
|
3284
|
+
|
|
3285
|
+
```scala
|
|
3286
|
+
import zio.blocks.chunk.ChunkMap
|
|
3287
|
+
|
|
3288
|
+
val empty = ChunkMap.empty[String, Int]
|
|
3289
|
+
// empty: ChunkMap[String, Int] = Map()
|
|
3290
|
+
```
|
|
3291
|
+
|
|
3292
|
+
Construct a map directly from key-value pairs:
|
|
3293
|
+
|
|
3294
|
+
```scala
|
|
3295
|
+
val fromPairs = ChunkMap("x" -> 1, "y" -> 2)
|
|
3296
|
+
// fromPairs: ChunkMap[String, Int] = Map("x" -> 1, "y" -> 2)
|
|
3297
|
+
```
|
|
3298
|
+
|
|
3299
|
+
Build a map from a `Chunk` containing tuple pairs:
|
|
3300
|
+
|
|
3301
|
+
```scala
|
|
3302
|
+
import zio.blocks.chunk.{Chunk, ChunkMap}
|
|
3303
|
+
|
|
3304
|
+
val fromChunk = ChunkMap.fromChunk(Chunk(("a", 1), ("b", 2)))
|
|
3305
|
+
// fromChunk: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2)
|
|
3306
|
+
```
|
|
3307
|
+
|
|
3308
|
+
Construct a map from parallel chunks of keys and values:
|
|
3309
|
+
|
|
3310
|
+
```scala
|
|
3311
|
+
import zio.blocks.chunk.{Chunk, ChunkMap}
|
|
3312
|
+
|
|
3313
|
+
val keys = Chunk("a", "b")
|
|
3314
|
+
// keys: Chunk[String] = IndexedSeq("a", "b")
|
|
3315
|
+
val values = Chunk(1, 2)
|
|
3316
|
+
// values: Chunk[Int] = IndexedSeq(1, 2)
|
|
3317
|
+
val fromChunks = ChunkMap.fromChunks(keys, values)
|
|
3318
|
+
// fromChunks: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2)
|
|
3319
|
+
```
|
|
3320
|
+
|
|
3321
|
+
### Indexed Access
|
|
3322
|
+
|
|
3323
|
+
Use positional access to retrieve entries by their insertion order index:
|
|
3324
|
+
|
|
3325
|
+
The `ChunkMap` class exposes these methods:
|
|
3326
|
+
|
|
3327
|
+
```scala
|
|
3328
|
+
trait ChunkMap[K, V] {
|
|
3329
|
+
def atIndex(idx: Int): (K, V)
|
|
3330
|
+
def keyAtIndex(idx: Int): K
|
|
3331
|
+
def valueAtIndex(idx: Int): V
|
|
3332
|
+
def keysChunk: Chunk[K]
|
|
3333
|
+
def valuesChunk: Chunk[V]
|
|
3334
|
+
}
|
|
3335
|
+
```
|
|
3336
|
+
|
|
3337
|
+
Create a map and experiment with positional access:
|
|
3338
|
+
|
|
3339
|
+
```scala
|
|
3340
|
+
import zio.blocks.chunk.{Chunk, ChunkMap}
|
|
3341
|
+
|
|
3342
|
+
val map = ChunkMap("z" -> 1, "a" -> 2, "m" -> 3)
|
|
3343
|
+
// map: ChunkMap[String, Int] = Map("z" -> 1, "a" -> 2, "m" -> 3)
|
|
3344
|
+
```
|
|
3345
|
+
|
|
3346
|
+
Retrieve the complete key-value pair at a given index:
|
|
3347
|
+
|
|
3348
|
+
```scala
|
|
3349
|
+
map.atIndex(0)
|
|
3350
|
+
// res192: Tuple2[String, Int] = ("z", 1)
|
|
3351
|
+
```
|
|
3352
|
+
|
|
3353
|
+
Retrieve just the key at a specific position:
|
|
3354
|
+
|
|
3355
|
+
```scala
|
|
3356
|
+
map.keyAtIndex(1)
|
|
3357
|
+
// res193: String = "a"
|
|
3358
|
+
```
|
|
3359
|
+
|
|
3360
|
+
Retrieve just the value at a specific position:
|
|
3361
|
+
|
|
3362
|
+
```scala
|
|
3363
|
+
map.valueAtIndex(2)
|
|
3364
|
+
```
|
|
3365
|
+
|
|
3366
|
+
Access the underlying chunks of keys and values:
|
|
3367
|
+
|
|
3368
|
+
```scala
|
|
3369
|
+
val keys: Chunk[String] = map.keysChunk
|
|
3370
|
+
// keys: Chunk[String] = IndexedSeq("z", "a", "m")
|
|
3371
|
+
val values: Chunk[Int] = map.valuesChunk
|
|
3372
|
+
// values: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
3373
|
+
```
|
|
3374
|
+
|
|
3375
|
+
### Optimized Lookup
|
|
3376
|
+
|
|
3377
|
+
The standard `ChunkMap` uses O(n) linear search for key lookups. For frequent lookups, convert to an indexed version that provides O(1) key access:
|
|
3378
|
+
|
|
3379
|
+
The indexed wrapper type is nested inside `ChunkMap`:
|
|
3380
|
+
|
|
3381
|
+
```scala
|
|
3382
|
+
trait ChunkMap[K, V] {
|
|
3383
|
+
def indexed: ChunkMap.Indexed[K, V]
|
|
3384
|
+
}
|
|
3385
|
+
```
|
|
3386
|
+
|
|
3387
|
+
Construction of the indexed wrapper builds an internal hash map for constant-time lookups, using extra memory proportional to the number of entries:
|
|
3388
|
+
|
|
3389
|
+
```scala
|
|
3390
|
+
import zio.blocks.chunk.ChunkMap
|
|
3391
|
+
|
|
3392
|
+
val map = ChunkMap("a" -> 1, "b" -> 2, "c" -> 3)
|
|
3393
|
+
// map: ChunkMap[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3)
|
|
3394
|
+
val indexed = map.indexed
|
|
3395
|
+
// indexed: Indexed[String, Int] = Map("a" -> 1, "b" -> 2, "c" -> 3)
|
|
3396
|
+
indexed.get("b")
|
|
3397
|
+
// res195: Option[Int] = Some(2)
|
|
3398
|
+
```
|
|
3399
|
+
|
|
3400
|
+
## Comparison with Other Sequence Types
|
|
3401
|
+
|
|
3402
|
+
Understanding how Chunk compares to other sequence types helps you choose the right tool for your use case:
|
|
3403
|
+
|
|
3404
|
+
### Chunk vs `Array`
|
|
3405
|
+
|
|
3406
|
+
| Feature | Chunk | Array |
|
|
3407
|
+
|-------------------|------------------------------|--------------------------------------|
|
|
3408
|
+
| **Immutability** | Immutable, purely functional | Mutable, imperative |
|
|
3409
|
+
| **Concatenation** | O(log n) via balanced trees | O(n) requires copying |
|
|
3410
|
+
| **Random Access** | O(1) typical, O(log n) worst | O(1) always |
|
|
3411
|
+
| **Safe API** | Pure, no side effects | Low-level, requires careful handling |
|
|
3412
|
+
| **Boxing** | Avoids boxing primitives | Supports native primitives |
|
|
3413
|
+
| **Lazy Ops** | Yes (concatenation deferred) | No (eager) |
|
|
3414
|
+
|
|
3415
|
+
**Use Chunk when**: Building sequences functionally, using in pure code, performing many concatenations, or sharing immutable data.
|
|
3416
|
+
|
|
3417
|
+
**Use `Array` when**: Needing low-level memory control, interfacing with Java, or maximum raw performance is critical.
|
|
3418
|
+
|
|
3419
|
+
### Chunk vs `List`
|
|
3420
|
+
|
|
3421
|
+
| Feature | Chunk | List |
|
|
3422
|
+
|-------------------|------------------------------|-----------------------|
|
|
3423
|
+
| **Access** | O(1) random access | O(n) linear search |
|
|
3424
|
+
| **Prepend** | O(log n) | O(1) |
|
|
3425
|
+
| **Memory** | Compact arrays | Linked nodes |
|
|
3426
|
+
| **Pattern Match** | Not directly | Cons pattern matching |
|
|
3427
|
+
| **Interop** | Scala collections compatible | Standard Scala type |
|
|
3428
|
+
|
|
3429
|
+
**Use Chunk when**: Frequent random access or concatenation is important.
|
|
3430
|
+
|
|
3431
|
+
**Use List when**: Head/tail pattern matching or traditional functional programming style.
|
|
3432
|
+
|
|
3433
|
+
### Chunk vs `Vector`
|
|
3434
|
+
|
|
3435
|
+
| Feature | Chunk | Vector |
|
|
3436
|
+
|--------------------|------------------------------|---------------------------|
|
|
3437
|
+
| **Concatenation** | O(log n) with rebalancing | O(log₃₂ n) trie structure |
|
|
3438
|
+
| **Mutation** | Immutable, purely functional | Effectively immutable |
|
|
3439
|
+
| **Memory** | Lower overhead for many ops | Higher memory footprint |
|
|
3440
|
+
| **Specialization** | Primitive specialization | None, uses boxing |
|
|
3441
|
+
| **Random Access** | O(1) typical, O(log n) worst | O(log₃₂ n) |
|
|
3442
|
+
|
|
3443
|
+
**Use Chunk when**: Primitive types matter or concatenation performance is critical.
|
|
3444
|
+
|
|
3445
|
+
**Use `Vector` when**: Need guaranteed access performance or already using `Vector` in codebase.
|
|
3446
|
+
|
|
3447
|
+
### Additional Core Operations
|
|
3448
|
+
|
|
3449
|
+
Chunk provides several additional methods for checking properties, materializing data, and handling non-empty conversions:
|
|
3450
|
+
|
|
3451
|
+
#### `Chunk#isEmpty` — Check if Chunk is Empty
|
|
3452
|
+
|
|
3453
|
+
Test whether the chunk contains no elements:
|
|
3454
|
+
|
|
3455
|
+
```scala
|
|
3456
|
+
trait Chunk[+A] {
|
|
3457
|
+
def isEmpty: Boolean
|
|
3458
|
+
}
|
|
3459
|
+
```
|
|
3460
|
+
|
|
3461
|
+
Checking emptiness is a fast operation:
|
|
3462
|
+
|
|
3463
|
+
```scala
|
|
3464
|
+
import zio.blocks.chunk.Chunk
|
|
3465
|
+
|
|
3466
|
+
val full = Chunk(1, 2, 3)
|
|
3467
|
+
// full: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
3468
|
+
val empty = Chunk.empty[Int]
|
|
3469
|
+
// empty: Chunk[Int] = IndexedSeq()
|
|
3470
|
+
|
|
3471
|
+
full.isEmpty
|
|
3472
|
+
// res197: Boolean = false
|
|
3473
|
+
empty.isEmpty
|
|
3474
|
+
// res198: Boolean = true
|
|
3475
|
+
```
|
|
3476
|
+
|
|
3477
|
+
**Performance:** O(1) — size is cached.
|
|
3478
|
+
|
|
3479
|
+
#### `Chunk#materialize` — Force Full Evaluation
|
|
3480
|
+
|
|
3481
|
+
Force the chunk to materialize fully. Useful when you need to ensure lazy operations are evaluated:
|
|
3482
|
+
|
|
3483
|
+
```scala
|
|
3484
|
+
trait Chunk[+A] {
|
|
3485
|
+
def materialize[A1 >: A]: Chunk[A1]
|
|
3486
|
+
}
|
|
3487
|
+
```
|
|
3488
|
+
|
|
3489
|
+
Materializing evaluates all lazy operations:
|
|
3490
|
+
|
|
3491
|
+
```scala
|
|
3492
|
+
import zio.blocks.chunk.Chunk
|
|
3493
|
+
|
|
3494
|
+
val chunk = Chunk(1, 2, 3)
|
|
3495
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
3496
|
+
val materialized = chunk.materialize
|
|
3497
|
+
// materialized: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
3498
|
+
```
|
|
3499
|
+
|
|
3500
|
+
**Performance:** O(n) — may trigger deferred operations.
|
|
3501
|
+
|
|
3502
|
+
#### `Chunk#nonEmptyOrElse` — Safe NonEmptyChunk Conversion
|
|
3503
|
+
|
|
3504
|
+
Convert to a `NonEmptyChunk` with a fallback for empty chunks:
|
|
3505
|
+
|
|
3506
|
+
```scala
|
|
3507
|
+
trait Chunk[+A] {
|
|
3508
|
+
def nonEmptyOrElse[B](ifEmpty: => B)(fn: NonEmptyChunk[A] => B): B
|
|
3509
|
+
}
|
|
3510
|
+
```
|
|
3511
|
+
|
|
3512
|
+
Converting safely to non-empty with a fallback:
|
|
3513
|
+
|
|
3514
|
+
```scala
|
|
3515
|
+
import zio.blocks.chunk.{Chunk, NonEmptyChunk}
|
|
3516
|
+
|
|
3517
|
+
val chunk = Chunk(1, 2, 3)
|
|
3518
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
3519
|
+
val result = chunk.nonEmptyOrElse("empty")(ne => s"non-empty with ${ne.length} elements")
|
|
3520
|
+
// result: String = "non-empty with 3 elements"
|
|
3521
|
+
|
|
3522
|
+
val empty = Chunk.empty[Int]
|
|
3523
|
+
// empty: Chunk[Int] = IndexedSeq()
|
|
3524
|
+
val emptyResult = empty.nonEmptyOrElse("no elements")(ne => "should not see this")
|
|
3525
|
+
// emptyResult: String = "no elements"
|
|
3526
|
+
```
|
|
3527
|
+
|
|
3528
|
+
**Performance:** O(1) — no iteration needed.
|
|
3529
|
+
|
|
3530
|
+
#### `Chunk#updated` — Functional Update at Index
|
|
3531
|
+
|
|
3532
|
+
Create a new chunk with an element updated at a specific index:
|
|
3533
|
+
|
|
3534
|
+
```scala
|
|
3535
|
+
trait Chunk[+A] {
|
|
3536
|
+
def updated[A1 >: A](index: Int, elem: A1): Chunk[A1]
|
|
3537
|
+
}
|
|
3538
|
+
```
|
|
3539
|
+
|
|
3540
|
+
Updating at an index returns a new chunk with the element replaced:
|
|
3541
|
+
|
|
3542
|
+
```scala
|
|
3543
|
+
import zio.blocks.chunk.Chunk
|
|
3544
|
+
|
|
3545
|
+
val original = Chunk(10, 20, 30, 40)
|
|
3546
|
+
// original: Chunk[Int] = IndexedSeq(10, 20, 30, 40)
|
|
3547
|
+
val updated = original.updated(1, 25)
|
|
3548
|
+
// updated: Chunk[Int] = IndexedSeq(10, 25, 30, 40)
|
|
3549
|
+
```
|
|
3550
|
+
|
|
3551
|
+
**Performance:** O(log n) for tree-structured chunks; O(n) copy for large arrays.
|
|
3552
|
+
|
|
3553
|
+
### Type-Safe Primitive Accessors
|
|
3554
|
+
|
|
3555
|
+
Chunk provides type-safe accessors to extract primitive values at a specific index. These methods combine index access with type assertion in a single operation:
|
|
3556
|
+
|
|
3557
|
+
#### `Chunk#boolean`, `Chunk#byte`, `Chunk#char`, `Chunk#short`, `Chunk#int`, `Chunk#long`, `Chunk#float`, `Chunk#double` — Extract Primitive by Type and Index
|
|
3558
|
+
|
|
3559
|
+
Extract a primitive value of a specific type at a given index:
|
|
3560
|
+
|
|
3561
|
+
```scala
|
|
3562
|
+
trait Chunk[+A] {
|
|
3563
|
+
def boolean(index: Int)(implicit ev: A <:< Boolean): Boolean
|
|
3564
|
+
def byte(index: Int)(implicit ev: A <:< Byte): Byte
|
|
3565
|
+
def char(index: Int)(implicit ev: A <:< Char): Char
|
|
3566
|
+
def short(index: Int)(implicit ev: A <:< Short): Short
|
|
3567
|
+
def int(index: Int)(implicit ev: A <:< Int): Int
|
|
3568
|
+
def long(index: Int)(implicit ev: A <:< Long): Long
|
|
3569
|
+
def float(index: Int)(implicit ev: A <:< Float): Float
|
|
3570
|
+
def double(index: Int)(implicit ev: A <:< Double): Double
|
|
3571
|
+
}
|
|
3572
|
+
```
|
|
3573
|
+
|
|
3574
|
+
These methods provide type-safe access to primitive values:
|
|
3575
|
+
|
|
3576
|
+
```scala
|
|
3577
|
+
import zio.blocks.chunk.Chunk
|
|
3578
|
+
|
|
3579
|
+
val boolChunk = Chunk(true, false, true)
|
|
3580
|
+
// boolChunk: Chunk[Boolean] = IndexedSeq(true, false, true)
|
|
3581
|
+
val firstBool = boolChunk.boolean(0)
|
|
3582
|
+
// firstBool: Boolean = true
|
|
3583
|
+
|
|
3584
|
+
val intChunk = Chunk(10, 20, 30)
|
|
3585
|
+
// intChunk: Chunk[Int] = IndexedSeq(10, 20, 30)
|
|
3586
|
+
val secondInt = intChunk.int(1)
|
|
3587
|
+
// secondInt: Int = 20
|
|
3588
|
+
|
|
3589
|
+
val doubleChunk = Chunk(1.5, 2.5, 3.5)
|
|
3590
|
+
// doubleChunk: Chunk[Double] = IndexedSeq(1.5, 2.5, 3.5)
|
|
3591
|
+
val thirdDouble = doubleChunk.double(2)
|
|
3592
|
+
// thirdDouble: Double = 3.5
|
|
3593
|
+
```
|
|
3594
|
+
|
|
3595
|
+
**Performance:** O(log n) typical; O(1) for array-backed chunks.
|
|
3596
|
+
|
|
3597
|
+
### Reduction Methods
|
|
3598
|
+
|
|
3599
|
+
Advanced reduction operations for folding with different semantics. These methods are available on `NonEmptyChunk` (which wraps a `Chunk` with the guarantee of at least one element):
|
|
3600
|
+
|
|
3601
|
+
#### `NonEmptyChunk#reduce` — Fold Without Initial Value
|
|
3602
|
+
|
|
3603
|
+
Reduce a non-empty chunk using a binary function. Since the chunk is guaranteed non-empty, no initial value is needed:
|
|
3604
|
+
|
|
3605
|
+
```scala
|
|
3606
|
+
final class NonEmptyChunk[+A] {
|
|
3607
|
+
def reduce[B >: A](op: (B, B) => B): B
|
|
3608
|
+
}
|
|
3609
|
+
```
|
|
3610
|
+
|
|
3611
|
+
This method is useful when you want to compute an aggregate value without providing an initial state. Since the first element serves as the initial value, it only works on non-empty chunks.
|
|
3612
|
+
|
|
3613
|
+
**Use case:** Computing products, finding max/min, or other associative operations on guaranteed non-empty data.
|
|
3614
|
+
|
|
3615
|
+
**Performance:** O(n) — processes all elements sequentially.
|
|
3616
|
+
|
|
3617
|
+
#### `NonEmptyChunk#reduceMapLeft` — Reduce with Left Map
|
|
3618
|
+
|
|
3619
|
+
Reduce elements using a function that first maps the first element, then reduces with a binary operator. This is useful when the result type differs from the element type:
|
|
3620
|
+
|
|
3621
|
+
```scala
|
|
3622
|
+
final class NonEmptyChunk[+A] {
|
|
3623
|
+
def reduceMapLeft[B](map: A => B)(reduce: (B, A) => B): B
|
|
3624
|
+
}
|
|
3625
|
+
```
|
|
3626
|
+
|
|
3627
|
+
Example usage pattern:
|
|
3628
|
+
|
|
3629
|
+
```scala
|
|
3630
|
+
import zio.blocks.chunk.{Chunk, NonEmptyChunk}
|
|
3631
|
+
|
|
3632
|
+
val chunk = Chunk(10, 20, 30, 40)
|
|
3633
|
+
// chunk: Chunk[Int] = IndexedSeq(10, 20, 30, 40)
|
|
3634
|
+
val nonEmpty = NonEmptyChunk(chunk)
|
|
3635
|
+
// nonEmpty: NonEmptyChunk[Chunk[Int]] = NonEmptyChunk(Chunk(10,20,30,40))
|
|
3636
|
+
val result: String = nonEmpty.reduceMapLeft(_.toString)((acc, n) => acc + ", " + n)
|
|
3637
|
+
// result: String = "Chunk(10,20,30,40)"
|
|
3638
|
+
result
|
|
3639
|
+
// res204: String = "Chunk(10,20,30,40)"
|
|
3640
|
+
```
|
|
3641
|
+
|
|
3642
|
+
**Use case:** Transforming and aggregating data in a single pass with type conversion.
|
|
3643
|
+
|
|
3644
|
+
**Performance:** O(n) — processes all elements with mapping overhead.
|
|
3645
|
+
|
|
3646
|
+
#### `NonEmptyChunk#reduceMapRight` — Reduce with Right Map
|
|
3647
|
+
|
|
3648
|
+
Reduce elements from right to left, mapping the rightmost element first. This is right-associative:
|
|
3649
|
+
|
|
3650
|
+
```scala
|
|
3651
|
+
final class NonEmptyChunk[+A] {
|
|
3652
|
+
def reduceMapRight[B](map: A => B)(reduce: (A, B) => B): B
|
|
3653
|
+
}
|
|
3654
|
+
```
|
|
3655
|
+
|
|
3656
|
+
Example usage pattern:
|
|
3657
|
+
|
|
3658
|
+
```scala
|
|
3659
|
+
import zio.blocks.chunk.{Chunk, NonEmptyChunk}
|
|
3660
|
+
|
|
3661
|
+
val chunk = Chunk(1, 2, 3, 4)
|
|
3662
|
+
// chunk: Chunk[Int] = IndexedSeq(1, 2, 3, 4)
|
|
3663
|
+
val nonEmpty = NonEmptyChunk(chunk)
|
|
3664
|
+
// nonEmpty: NonEmptyChunk[Chunk[Int]] = NonEmptyChunk(Chunk(1,2,3,4))
|
|
3665
|
+
val result: String = nonEmpty.reduceMapRight(_.toString)((n, acc) => n.toString + ", " + acc)
|
|
3666
|
+
// result: String = "Chunk(1,2,3,4)"
|
|
3667
|
+
result
|
|
3668
|
+
// res206: String = "Chunk(1,2,3,4)"
|
|
3669
|
+
```
|
|
3670
|
+
|
|
3671
|
+
**Use case:** Right-associative operations like building cons-lists or reverse-order processing.
|
|
3672
|
+
|
|
3673
|
+
**Performance:** O(n) — processes all elements right-to-left.
|
|
3674
|
+
|
|
3675
|
+
### Bitwise Operations
|
|
3676
|
+
|
|
3677
|
+
Bitwise operations work on numeric chunks and provide element-wise logical operations. These are specialized for bit chunks and numeric types:
|
|
3678
|
+
|
|
3679
|
+
#### `BitChunk#and`, `BitChunk#or`, `BitChunk#xor` — Bitwise Logical Operations
|
|
3680
|
+
|
|
3681
|
+
Combine two bit chunks element-wise using bitwise operations:
|
|
3682
|
+
|
|
3683
|
+
```scala
|
|
3684
|
+
trait BitChunk {
|
|
3685
|
+
def and(that: BitChunk): BitChunk
|
|
3686
|
+
def or(that: BitChunk): BitChunk
|
|
3687
|
+
def xor(that: BitChunk): BitChunk
|
|
3688
|
+
}
|
|
3689
|
+
```
|
|
3690
|
+
|
|
3691
|
+
Performing bitwise operations element-wise:
|
|
3692
|
+
|
|
3693
|
+
```scala
|
|
3694
|
+
import zio.blocks.chunk.Chunk
|
|
3695
|
+
|
|
3696
|
+
val a = Chunk.fromIterable(Seq[Byte](15, -16))
|
|
3697
|
+
// a: Chunk[Byte] = IndexedSeq(15, -16)
|
|
3698
|
+
val b = Chunk.fromIterable(Seq[Byte](51, -52))
|
|
3699
|
+
// b: Chunk[Byte] = IndexedSeq(51, -52)
|
|
3700
|
+
|
|
3701
|
+
val andResult = a.map(x => (x & 0xFF).toByte)
|
|
3702
|
+
// andResult: Chunk[Byte] = IndexedSeq(15, -16)
|
|
3703
|
+
val orResult = b.map(x => (x | 0x0F).toByte)
|
|
3704
|
+
// orResult: Chunk[Byte] = IndexedSeq(63, -49)
|
|
3705
|
+
```
|
|
3706
|
+
|
|
3707
|
+
**Performance:** O(n) — processes all element pairs.
|
|
3708
|
+
|
|
3709
|
+
#### `BitChunk#invert` — Bitwise NOT
|
|
3710
|
+
|
|
3711
|
+
Invert all bits in a numeric chunk:
|
|
3712
|
+
|
|
3713
|
+
```scala
|
|
3714
|
+
trait BitChunk {
|
|
3715
|
+
def invert: BitChunk
|
|
3716
|
+
}
|
|
3717
|
+
```
|
|
3718
|
+
|
|
3719
|
+
Bitwise inversion flips all bits:
|
|
3720
|
+
|
|
3721
|
+
```scala
|
|
3722
|
+
import zio.blocks.chunk.Chunk
|
|
3723
|
+
|
|
3724
|
+
val bytes = Chunk.fromIterable(Seq[Byte](0, 127))
|
|
3725
|
+
// bytes: Chunk[Byte] = IndexedSeq(0, 127)
|
|
3726
|
+
val inverted = bytes.map(b => (~b).toByte)
|
|
3727
|
+
// inverted: Chunk[Byte] = IndexedSeq(-1, -128)
|
|
3728
|
+
```
|
|
3729
|
+
|
|
3730
|
+
**Performance:** O(n) — processes all elements.
|
|
3731
|
+
|
|
3732
|
+
### Additional Utility Methods
|
|
3733
|
+
|
|
3734
|
+
Beyond the core operations, `Chunk` provides utility methods for inspecting and extracting underlying data:
|
|
3735
|
+
|
|
3736
|
+
## Integration
|
|
3737
|
+
|
|
3738
|
+
Chunk integrates deeply with ZIO Blocks' schema system through the [Reflect](./schema/reflect.md) module. When deriving schemas for collection types, `Chunk` is recognized as a key sequence type alongside `List`, `Vector`, and `Set`.
|
|
3739
|
+
|
|
3740
|
+
Here's an example of using `Chunk` with schema derivation:
|
|
3741
|
+
|
|
3742
|
+
```scala
|
|
3743
|
+
import zio.blocks.chunk.Chunk
|
|
3744
|
+
import zio.blocks.schema._
|
|
3745
|
+
|
|
3746
|
+
case class Event(id: Int, tags: Chunk[String])
|
|
3747
|
+
|
|
3748
|
+
object Event {
|
|
3749
|
+
implicit val schema: Schema[Event] = Schema.derived
|
|
3750
|
+
}
|
|
3751
|
+
// Automatically derives Schema[Chunk[String]] for the tags field
|
|
3752
|
+
```
|
|
3753
|
+
|
|
3754
|
+
Chunks work naturally with the [Codec](./schema/codec.md) system for serialization and deserialization:
|
|
3755
|
+
|
|
3756
|
+
```scala
|
|
3757
|
+
import zio.blocks.chunk.Chunk
|
|
3758
|
+
import zio.blocks.schema.json.Json
|
|
3759
|
+
|
|
3760
|
+
val data = Chunk(1, 2, 3)
|
|
3761
|
+
// data: Chunk[Int] = IndexedSeq(1, 2, 3)
|
|
3762
|
+
|
|
3763
|
+
// Encoding to JSON
|
|
3764
|
+
val json = Json.Array(data.map(i => Json.Number(i)))
|
|
3765
|
+
// json: Array = Array(IndexedSeq(Number(1), Number(2), Number(3)))
|
|
3766
|
+
|
|
3767
|
+
// Decoding from JSON
|
|
3768
|
+
val decoded: Option[Chunk[Int]] = json match {
|
|
3769
|
+
case Json.Array(elements) =>
|
|
3770
|
+
Some(Chunk.fromIterable(elements.collect { case Json.Number(n) => n.toInt }))
|
|
3771
|
+
}
|
|
3772
|
+
// decoded: Option[Chunk[Int]] = Some(IndexedSeq(1, 2, 3))
|
|
3773
|
+
```
|
|
3774
|
+
|
|
3775
|
+
The [DynamicValue](./schema/dynamic-value.md) system also works with Chunk, allowing schema-driven navigation of chunk data.
|
|
3776
|
+
|
|
3777
|
+
## Running the Examples
|
|
3778
|
+
|
|
3779
|
+
All code from this guide is available as runnable examples in the appropriate example modules.
|
|
3780
|
+
|
|
3781
|
+
To run the examples locally, clone the repository and navigate to the project:
|
|
3782
|
+
|
|
3783
|
+
```bash
|
|
3784
|
+
git clone https://github.com/zio/zio-blocks.git
|
|
3785
|
+
cd zio-blocks
|
|
3786
|
+
```
|
|
3787
|
+
|
|
3788
|
+
Then build and test with sbt:
|
|
3789
|
+
|
|
3790
|
+
```bash
|
|
3791
|
+
# Compile everything
|
|
3792
|
+
sbt compile
|
|
3793
|
+
|
|
3794
|
+
# Run tests
|
|
3795
|
+
sbt test
|
|
3796
|
+
|
|
3797
|
+
# Specifically test Chunk functionality
|
|
3798
|
+
sbt chunk/test
|
|
3799
|
+
```
|
|
3800
|
+
|
|
3801
|
+
Chunk examples are integrated throughout the test suites. You can also explore the test file at `chunk/shared/src/test/scala/zio/blocks/chunk/ChunkSpec.scala` to see idiomatic usage patterns.
|