or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

array-operations.mdawareness.mddocument-management.mdindex.mdmap-operations.mdposition-undo.mdsynchronization.mdtext-operations.mdxml-support.md

array-operations.mddocs/

0

# Array Operations

1

2

## Overview

3

4

The `Array` type in pycrdt provides collaborative array/list functionality with automatic conflict resolution across multiple clients. It supports a complete list-like interface with additional collaborative features like move operations, change tracking, and type-safe variants.

5

6

## Core Types

7

8

### Array

9

10

Collaborative array with list-like interface and change tracking.

11

12

```python { .api }

13

class Array[T]:

14

def __init__(

15

self,

16

init: list[T] | None = None,

17

*,

18

_doc: Doc | None = None,

19

_integrated: _Array | None = None,

20

) -> None:

21

"""

22

Create a new collaborative array.

23

24

Args:

25

init (list, optional): Initial array contents

26

_doc (Doc, optional): Parent document

27

_integrated (_Array, optional): Native array instance

28

"""

29

30

# List-like interface

31

def __len__(self) -> int:

32

"""Get the length of the array."""

33

34

def __str__(self) -> str:

35

"""Get string representation of the array."""

36

37

def __iter__(self) -> ArrayIterator:

38

"""Iterate over array elements."""

39

40

def __contains__(self, item: T) -> bool:

41

"""Check if item exists in array."""

42

43

def __getitem__(self, key: int | slice) -> T | list[T]:

44

"""Get element or slice by index."""

45

46

def __setitem__(self, key: int | slice, value: T | list[T]) -> None:

47

"""Set element or slice by index."""

48

49

def __delitem__(self, key: int | slice) -> None:

50

"""Delete element or slice by index."""

51

52

def __add__(self, value: list[T]) -> Array[T]:

53

"""Concatenate with another list."""

54

55

def __radd__(self, value: list[T]) -> Array[T]:

56

"""Right-side concatenation with another list."""

57

58

# Array manipulation methods

59

def append(self, value: T) -> None:

60

"""

61

Append an element to the end of the array.

62

63

Args:

64

value: Element to append

65

"""

66

67

def extend(self, value: list[T]) -> None:

68

"""

69

Extend the array with elements from an iterable.

70

71

Args:

72

value (list): Elements to add to the array

73

"""

74

75

def insert(self, index: int, object: T) -> None:

76

"""

77

Insert an element at the specified index.

78

79

Args:

80

index (int): Position to insert element

81

object: Element to insert

82

"""

83

84

def pop(self, index: int = -1) -> T:

85

"""

86

Remove and return element at index (default last).

87

88

Args:

89

index (int): Index of element to remove

90

91

Returns:

92

T: Removed element

93

"""

94

95

def move(self, source_index: int, destination_index: int) -> None:

96

"""

97

Move an element from source to destination index.

98

99

Args:

100

source_index (int): Current position of element

101

destination_index (int): New position for element

102

"""

103

104

def clear(self) -> None:

105

"""Remove all elements from the array."""

106

107

def to_py(self) -> list[T] | None:

108

"""

109

Convert array to a Python list.

110

111

Returns:

112

list | None: Array contents as list, or None if empty

113

"""

114

115

def observe(self, callback: Callable[[ArrayEvent], None]) -> Subscription:

116

"""

117

Observe array changes.

118

119

Args:

120

callback: Function called when array changes occur

121

122

Returns:

123

Subscription: Handle for unsubscribing

124

"""

125

126

def observe_deep(self, callback: Callable[[list[ArrayEvent]], None]) -> Subscription:

127

"""

128

Observe deep changes including nested structures.

129

130

Args:

131

callback: Function called with list of change events

132

133

Returns:

134

Subscription: Handle for unsubscribing

135

"""

136

137

def unobserve(self, subscription: Subscription) -> None:

138

"""

139

Remove an event observer.

140

141

Args:

142

subscription: Subscription handle to remove

143

"""

144

145

async def events(

146

self,

147

deep: bool = False,

148

max_buffer_size: float = float("inf")

149

) -> MemoryObjectReceiveStream:

150

"""

151

Get an async stream of array events.

152

153

Args:

154

deep (bool): Include deep change events

155

max_buffer_size (float): Maximum event buffer size

156

157

Returns:

158

MemoryObjectReceiveStream: Async event stream

159

"""

160

161

def sticky_index(self, index: int, assoc: Assoc = Assoc.AFTER) -> StickyIndex:

162

"""

163

Create a sticky index that maintains its position during edits.

164

165

Args:

166

index (int): Initial index position

167

assoc (Assoc): Association type (BEFORE or AFTER)

168

169

Returns:

170

StickyIndex: Persistent position tracker

171

"""

172

```

173

174

### ArrayEvent

175

176

Event emitted when array changes occur.

177

178

```python { .api }

179

class ArrayEvent:

180

@property

181

def target(self) -> Array:

182

"""Get the array that changed."""

183

184

@property

185

def delta(self) -> list[dict[str, Any]]:

186

"""

187

Get the delta describing the changes.

188

189

Delta format:

190

- {"retain": n} - Keep n elements unchanged

191

- {"insert": [items]} - Insert elements

192

- {"delete": n} - Delete n elements

193

"""

194

195

@property

196

def path(self) -> list[int | str]:

197

"""Get the path to the changed array within the document structure."""

198

```

199

200

### TypedArray

201

202

Type-safe wrapper for Array with typed elements.

203

204

```python { .api }

205

class TypedArray[T]:

206

"""

207

Type-safe array container with runtime type checking.

208

209

Usage:

210

class StringArray(TypedArray[str]):

211

type = str # Define element type

212

213

array = StringArray()

214

array.append("hello") # Type-safe

215

item: str = array[0] # Typed access

216

"""

217

```

218

219

## Usage Examples

220

221

### Basic Array Operations

222

223

```python

224

from pycrdt import Doc, Array

225

226

doc = Doc()

227

array = doc.get("items", type=Array)

228

229

# Basic list operations

230

array.append("item1")

231

array.append("item2")

232

array.extend(["item3", "item4"])

233

print(len(array)) # 4

234

235

# List-like access

236

print(array[0]) # "item1"

237

print(array[-1]) # "item4"

238

print(array[1:3]) # ["item2", "item3"]

239

240

# Modification

241

array[1] = "modified_item2"

242

array.insert(2, "inserted_item")

243

244

# Check contents

245

print("item1" in array) # True

246

print(list(array)) # All elements

247

```

248

249

### Array Manipulation

250

251

```python

252

from pycrdt import Doc, Array

253

254

doc = Doc()

255

numbers = doc.get("numbers", type=Array)

256

257

# Build array

258

numbers.extend([1, 2, 3, 4, 5])

259

260

# Move operations (unique to collaborative arrays)

261

numbers.move(0, 4) # Move first element to end

262

print(list(numbers)) # [2, 3, 4, 5, 1]

263

264

# Pop operations

265

last = numbers.pop() # Remove and return last element

266

first = numbers.pop(0) # Remove and return first element

267

print(f"Removed: {first}, {last}")

268

269

# Slice operations

270

numbers[1:3] = [10, 20, 30] # Replace slice

271

del numbers[2:4] # Delete slice

272

```

273

274

### Nested Data Structures

275

276

```python

277

from pycrdt import Doc, Array, Map

278

279

doc = Doc()

280

users = doc.get("users", type=Array)

281

282

# Add user objects

283

user1 = Map()

284

user1["name"] = "Alice"

285

user1["age"] = 30

286

users.append(user1)

287

288

user2 = Map()

289

user2["name"] = "Bob"

290

user2["age"] = 25

291

users.append(user2)

292

293

# Access nested data

294

print(users[0]["name"]) # "Alice"

295

print(users[1]["age"]) # 25

296

297

# Modify nested structures

298

users[0]["age"] = 31

299

print(users[0]["age"]) # 31

300

```

301

302

### Type-Safe Arrays

303

304

```python

305

from pycrdt import TypedArray, Doc

306

307

class NumberList(TypedArray[int]):

308

type = int

309

310

class StringList(TypedArray[str]):

311

type = str

312

313

doc = Doc()

314

315

# Create typed arrays

316

numbers = NumberList()

317

strings = StringList()

318

319

# Type-safe operations

320

numbers.append(42) # OK

321

strings.append("hello") # OK

322

323

try:

324

numbers.append("string") # May raise TypeError

325

except TypeError as e:

326

print(f"Type error: {e}")

327

328

# Typed access

329

first_number: int = numbers[0] # Typed

330

first_string: str = strings[0] # Typed

331

```

332

333

### Position Tracking

334

335

```python

336

from pycrdt import Doc, Array, Assoc

337

338

doc = Doc()

339

tasks = doc.get("tasks", type=Array)

340

341

tasks.extend(["Task 1", "Task 2", "Task 3", "Task 4"])

342

343

# Create sticky indices

344

important_pos = tasks.sticky_index(1, Assoc.BEFORE) # Before "Task 2"

345

end_pos = tasks.sticky_index(3, Assoc.AFTER) # After "Task 3"

346

347

# Insert elements

348

tasks.insert(0, "Urgent Task")

349

tasks.append("Final Task")

350

351

# Check positions (they maintain relative positions)

352

with doc.transaction() as txn:

353

important_idx = important_pos.get_index(txn)

354

end_idx = end_pos.get_index(txn)

355

print(f"Important task at: {important_idx}") # Adjusted index

356

print(f"End position at: {end_idx}") # Adjusted index

357

```

358

359

### Event Observation

360

361

```python

362

from pycrdt import Doc, Array, ArrayEvent

363

364

doc = Doc()

365

array = doc.get("items", type=Array)

366

367

def on_array_change(event: ArrayEvent):

368

print(f"Array changed: {event.target}")

369

print(f"Delta: {event.delta}")

370

for op in event.delta:

371

if "retain" in op:

372

print(f" Retain {op['retain']} elements")

373

elif "insert" in op:

374

items = op["insert"]

375

print(f" Insert {len(items)} elements: {items}")

376

elif "delete" in op:

377

print(f" Delete {op['delete']} elements")

378

379

# Subscribe to changes

380

subscription = array.observe(on_array_change)

381

382

# Make changes to trigger events

383

array.append("item1")

384

array.extend(["item2", "item3"])

385

array.move(0, 2)

386

array.pop()

387

388

# Clean up

389

array.unobserve(subscription)

390

```

391

392

### Async Event Streaming

393

394

```python

395

import anyio

396

from pycrdt import Doc, Array

397

398

async def monitor_array_changes(array: Array):

399

async with array.events() as event_stream:

400

async for event in event_stream:

401

print(f"Array event: {event.delta}")

402

403

doc = Doc()

404

array = doc.get("items", type=Array)

405

406

async def main():

407

async with anyio.create_task_group() as tg:

408

tg.start_soon(monitor_array_changes, array)

409

410

# Make changes

411

await anyio.sleep(0.1)

412

array.append("item1")

413

await anyio.sleep(0.1)

414

array.extend(["item2", "item3"])

415

await anyio.sleep(0.1)

416

417

anyio.run(main)

418

```

419

420

### Collaborative Array Editing

421

422

```python

423

from pycrdt import Doc, Array

424

425

# Simulate two clients editing the same array

426

doc1 = Doc(client_id=1)

427

doc2 = Doc(client_id=2)

428

429

array1 = doc1.get("shared_list", type=Array)

430

array2 = doc2.get("shared_list", type=Array)

431

432

# Client 1 adds items

433

with doc1.transaction(origin="client1"):

434

array1.extend([1, 2, 3])

435

436

# Sync to client 2

437

update = doc1.get_update()

438

doc2.apply_update(update)

439

print(list(array2)) # [1, 2, 3]

440

441

# Client 2 makes concurrent changes

442

with doc2.transaction(origin="client2"):

443

array2.insert(0, 0) # Insert at beginning

444

array2.append(4) # Add to end

445

array2.move(2, 0) # Move element

446

447

# Sync back to client 1

448

update = doc2.get_update(doc1.get_state())

449

doc1.apply_update(update)

450

451

# Both clients now have the same state

452

print(f"Client 1: {list(array1)}")

453

print(f"Client 2: {list(array2)}")

454

```

455

456

### Complex Data Processing

457

458

```python

459

from pycrdt import Doc, Array, Map

460

461

doc = Doc()

462

inventory = doc.get("inventory", type=Array)

463

464

# Build complex inventory data

465

items = [

466

{"name": "Widget A", "price": 10.99, "quantity": 50},

467

{"name": "Widget B", "price": 15.99, "quantity": 30},

468

{"name": "Widget C", "price": 8.99, "quantity": 75},

469

]

470

471

for item_data in items:

472

item = Map()

473

for key, value in item_data.items():

474

item[key] = value

475

inventory.append(item)

476

477

# Process inventory

478

def calculate_total_value(inventory: Array) -> float:

479

"""Calculate total inventory value."""

480

total = 0.0

481

for item in inventory:

482

price = item["price"]

483

quantity = item["quantity"]

484

total += price * quantity

485

return total

486

487

print(f"Total inventory value: ${calculate_total_value(inventory):.2f}")

488

489

# Update quantities

490

def update_quantity(inventory: Array, name: str, new_quantity: int):

491

"""Update quantity for a specific item."""

492

for item in inventory:

493

if item["name"] == name:

494

item["quantity"] = new_quantity

495

break

496

497

update_quantity(inventory, "Widget A", 45)

498

print(f"Updated inventory value: ${calculate_total_value(inventory):.2f}")

499

500

# Add new items

501

new_item = Map()

502

new_item["name"] = "Widget D"

503

new_item["price"] = 12.99

504

new_item["quantity"] = 20

505

inventory.append(new_item)

506

```

507

508

### Array Sorting and Filtering

509

510

```python

511

from pycrdt import Doc, Array

512

513

doc = Doc()

514

numbers = doc.get("numbers", type=Array)

515

numbers.extend([3, 1, 4, 1, 5, 9, 2, 6])

516

517

# Sort array (collaborative way)

518

def collaborative_sort(array: Array, key=None, reverse=False):

519

"""Sort array in place using collaborative moves."""

520

# Get current elements

521

elements = list(array)

522

523

# Get sorted indices

524

sorted_indices = sorted(range(len(elements)),

525

key=lambda i: elements[i] if key is None else key(elements[i]),

526

reverse=reverse)

527

528

# Apply moves to achieve sorted order

529

for target_pos, source_pos in enumerate(sorted_indices):

530

if source_pos != target_pos:

531

# Find current position of the element we want to move

532

current_pos = source_pos

533

for i, idx in enumerate(sorted_indices[:target_pos]):

534

if idx < source_pos:

535

current_pos -= 1

536

537

if current_pos != target_pos:

538

array.move(current_pos, target_pos)

539

540

# Sort the array

541

collaborative_sort(numbers)

542

print(f"Sorted: {list(numbers)}")

543

544

# Filter and rebuild (non-collaborative approach)

545

def filter_array(array: Array, predicate) -> Array:

546

"""Create new array with filtered elements."""

547

filtered = Array()

548

for element in array:

549

if predicate(element):

550

filtered.append(element)

551

return filtered

552

553

even_numbers = filter_array(numbers, lambda x: x % 2 == 0)

554

print(f"Even numbers: {list(even_numbers)}")

555

```

556

557

## Delta Operations

558

559

Array changes are represented as delta operations:

560

561

```python

562

# Example delta operations

563

delta_examples = [

564

{"retain": 2}, # Keep 2 elements

565

{"insert": ["a", "b"]}, # Insert elements

566

{"delete": 1}, # Delete 1 element

567

]

568

569

# Processing deltas

570

def apply_delta(array: Array, delta: list[dict]):

571

"""Apply a delta to array (conceptual example)."""

572

pos = 0

573

for op in delta:

574

if "retain" in op:

575

pos += op["retain"]

576

elif "insert" in op:

577

items = op["insert"]

578

for i, item in enumerate(items):

579

array.insert(pos + i, item)

580

pos += len(items)

581

elif "delete" in op:

582

for _ in range(op["delete"]):

583

del array[pos]

584

```

585

586

## Error Handling

587

588

```python

589

from pycrdt import Doc, Array

590

591

doc = Doc()

592

array = doc.get("items", type=Array)

593

594

try:

595

# Invalid index operations

596

array.insert(-1, "invalid") # May raise ValueError

597

598

# Out of bounds access

599

item = array[100] # May raise IndexError

600

601

# Invalid move operations

602

array.move(0, 100) # May raise ValueError

603

604

# Pop from empty array

605

array.clear()

606

array.pop() # May raise IndexError

607

608

except (ValueError, IndexError, TypeError) as e:

609

print(f"Array operation failed: {e}")

610

```