or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-collections.mdbags.mdbidimaps.mdcollection-utilities.mdfunctional-programming.mdindex.mdmultimaps.md

multimaps.mddocs/

0

# Multi-Valued Maps (MultiMaps)

1

2

Multi-valued maps allow multiple values to be associated with each key. Unlike regular maps which have a 1:1 key-value relationship, multi-valued maps support 1:many relationships where each key can map to a collection of values.

3

4

## Core Interfaces

5

6

### MultiValuedMap<K, V> Interface

7

8

The primary interface for multi-valued maps that associates collections of values with keys.

9

10

```java { .api }

11

import org.apache.commons.collections4.MultiValuedMap;

12

import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;

13

import java.util.Collection;

14

15

MultiValuedMap<String, String> multiMap = new ArrayListValuedHashMap<>();

16

17

// Add multiple values for the same key

18

multiMap.put("colors", "red");

19

multiMap.put("colors", "green");

20

multiMap.put("colors", "blue");

21

multiMap.put("animals", "cat");

22

multiMap.put("animals", "dog");

23

24

// Get all values for a key (returns Collection<V>)

25

Collection<String> colors = multiMap.get("colors"); // ["red", "green", "blue"]

26

Collection<String> animals = multiMap.get("animals"); // ["cat", "dog"]

27

Collection<String> empty = multiMap.get("plants"); // [] (empty collection)

28

29

// Check presence

30

boolean hasColors = multiMap.containsKey("colors"); // true

31

boolean hasRed = multiMap.containsValue("red"); // true

32

boolean hasMapping = multiMap.containsMapping("colors", "red"); // true

33

34

// Size operations

35

int totalMappings = multiMap.size(); // 5 (total key-value pairs)

36

int uniqueKeys = multiMap.keySet().size(); // 2 (colors, animals)

37

boolean isEmpty = multiMap.isEmpty(); // false

38

39

// Remove operations

40

boolean removed = multiMap.removeMapping("colors", "red"); // Remove specific mapping

41

Collection<String> removedColors = multiMap.remove("colors"); // Remove all values for key

42

```

43

44

### ListValuedMap<K, V> Interface

45

46

A multi-valued map where each key maps to a List of values, preserving order and allowing duplicates.

47

48

```java { .api }

49

import org.apache.commons.collections4.ListValuedMap;

50

import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;

51

import java.util.List;

52

53

ListValuedMap<String, Integer> scores = new ArrayListValuedHashMap<>();

54

55

// Add scores in order (duplicates allowed)

56

scores.put("Alice", 85);

57

scores.put("Alice", 92);

58

scores.put("Alice", 88);

59

scores.put("Alice", 92); // Duplicate allowed

60

61

// Get as List (preserves order and duplicates)

62

List<Integer> aliceScores = scores.get("Alice"); // [85, 92, 88, 92]

63

64

// List-specific operations on values

65

aliceScores.add(95); // Modifies underlying multimap

66

int firstScore = aliceScores.get(0); // 85

67

int lastScore = aliceScores.get(aliceScores.size() - 1); // 95

68

```

69

70

### SetValuedMap<K, V> Interface

71

72

A multi-valued map where each key maps to a Set of values, ensuring uniqueness.

73

74

```java { .api }

75

import org.apache.commons.collections4.SetValuedMap;

76

import org.apache.commons.collections4.multimap.HashSetValuedHashMap;

77

import java.util.Set;

78

79

SetValuedMap<String, String> permissions = new HashSetValuedHashMap<>();

80

81

// Add permissions (duplicates automatically ignored)

82

permissions.put("admin", "read");

83

permissions.put("admin", "write");

84

permissions.put("admin", "delete");

85

permissions.put("admin", "read"); // Duplicate ignored

86

87

// Get as Set (no duplicates)

88

Set<String> adminPerms = permissions.get("admin"); // ["read", "write", "delete"]

89

90

// Set-specific operations

91

boolean hasReadPerm = adminPerms.contains("read"); // true

92

int uniquePerms = adminPerms.size(); // 3

93

```

94

95

## Concrete Implementations

96

97

### ArrayListValuedHashMap<K, V>

98

99

HashMap-based implementation that stores values in ArrayList collections.

100

101

```java { .api }

102

import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;

103

import java.util.Arrays;

104

import java.util.List;

105

106

ArrayListValuedHashMap<String, String> topics = new ArrayListValuedHashMap<>();

107

108

// Bulk operations

109

topics.putAll("java", Arrays.asList("collections", "streams", "generics"));

110

topics.putAll("python", Arrays.asList("lists", "dictionaries", "comprehensions"));

111

112

// Values maintain insertion order and allow duplicates

113

topics.put("java", "collections"); // Duplicate allowed

114

List<String> javaTopics = topics.get("java"); // ["collections", "streams", "generics", "collections"]

115

116

// Capacity optimization for known sizes

117

ArrayListValuedHashMap<String, Integer> optimized = new ArrayListValuedHashMap<>();

118

// Internal ArrayLists start with default capacity

119

```

120

121

### HashSetValuedHashMap<K, V>

122

123

HashMap-based implementation that stores values in HashSet collections.

124

125

```java { .api }

126

import org.apache.commons.collections4.multimap.HashSetValuedHashMap;

127

import java.util.Arrays;

128

import java.util.Set;

129

130

HashSetValuedHashMap<String, String> tags = new HashSetValuedHashMap<>();

131

132

// Bulk operations with automatic deduplication

133

tags.putAll("article1", Arrays.asList("java", "tutorial", "beginner", "java")); // Duplicate "java" ignored

134

tags.putAll("article2", Arrays.asList("python", "advanced", "tutorial"));

135

136

// Values are unique within each key

137

Set<String> article1Tags = tags.get("article1"); // ["java", "tutorial", "beginner"]

138

139

// Fast contains operations (Set performance)

140

boolean hasJavaTag = tags.containsMapping("article1", "java"); // true - O(1) average case

141

```

142

143

## Multi-Valued Map Operations

144

145

### Adding Values

146

147

```java { .api }

148

MultiValuedMap<String, String> contacts = new ArrayListValuedHashMap<>();

149

150

// Single value additions

151

contacts.put("John", "john@work.com");

152

contacts.put("John", "john@personal.com");

153

contacts.put("John", "john@mobile.com");

154

155

// Bulk additions

156

contacts.putAll("Jane", Arrays.asList("jane@work.com", "jane@home.com"));

157

158

// Add to existing collection

159

Collection<String> johnEmails = contacts.get("John");

160

johnEmails.add("john@backup.com"); // Modifies underlying multimap

161

162

// Conditional addition

163

if (!contacts.containsMapping("John", "john@spam.com")) {

164

contacts.put("John", "john@spam.com");

165

}

166

```

167

168

### Removing Values

169

170

```java { .api }

171

// Remove specific key-value mapping

172

boolean removed = contacts.removeMapping("John", "john@mobile.com"); // true

173

boolean notRemoved = contacts.removeMapping("John", "nonexistent"); // false

174

175

// Remove all values for a key

176

Collection<String> janeEmails = contacts.remove("Jane"); // Returns and removes all values

177

// janeEmails = ["jane@work.com", "jane@home.com"]

178

179

// Remove through collection view

180

Collection<String> johnEmails = contacts.get("John");

181

johnEmails.remove("john@work.com"); // Modifies underlying multimap

182

183

// Clear all mappings

184

contacts.clear();

185

```

186

187

### Querying and Iteration

188

189

```java { .api }

190

MultiValuedMap<String, Integer> studentGrades = new ArrayListValuedHashMap<>();

191

studentGrades.putAll("Alice", Arrays.asList(85, 92, 78, 95));

192

studentGrades.putAll("Bob", Arrays.asList(88, 76, 82));

193

194

// Key iteration

195

for (String student : studentGrades.keySet()) {

196

System.out.println("Student: " + student);

197

}

198

199

// Value iteration (all values)

200

for (Integer grade : studentGrades.values()) {

201

System.out.println("Grade: " + grade);

202

}

203

204

// Entry iteration (key-value pairs)

205

for (Map.Entry<String, Integer> entry : studentGrades.entries()) {

206

System.out.println(entry.getKey() + " scored " + entry.getValue());

207

}

208

209

// Key-collection iteration

210

for (Map.Entry<String, Collection<Integer>> entry : studentGrades.asMap().entrySet()) {

211

String student = entry.getKey();

212

Collection<Integer> grades = entry.getValue();

213

double average = grades.stream().mapToInt(Integer::intValue).average().orElse(0.0);

214

System.out.println(student + " average: " + average);

215

}

216

```

217

218

## Advanced Usage Patterns

219

220

### Grouping and Classification

221

222

```java { .api }

223

import java.util.stream.Collectors;

224

225

public class StudentManager {

226

private final MultiValuedMap<String, Student> studentsByGrade = new ArrayListValuedHashMap<>();

227

private final MultiValuedMap<String, Student> studentsBySubject = new HashSetValuedHashMap<>();

228

229

public void addStudent(Student student) {

230

// Group by grade level

231

studentsByGrade.put(student.getGrade(), student);

232

233

// Group by subjects (Set semantics - each student appears once per subject)

234

for (String subject : student.getSubjects()) {

235

studentsBySubject.put(subject, student);

236

}

237

}

238

239

public List<Student> getStudentsInGrade(String grade) {

240

return new ArrayList<>(studentsByGrade.get(grade));

241

}

242

243

public Set<Student> getStudentsInSubject(String subject) {

244

return new HashSet<>(studentsBySubject.get(subject));

245

}

246

247

public Map<String, Long> getGradeDistribution() {

248

return studentsByGrade.asMap().entrySet().stream()

249

.collect(Collectors.toMap(

250

Map.Entry::getKey,

251

entry -> (long) entry.getValue().size()

252

));

253

}

254

}

255

```

256

257

### Configuration Management

258

259

```java { .api }

260

public class ApplicationConfig {

261

private final MultiValuedMap<String, String> properties = new ArrayListValuedHashMap<>();

262

263

public void loadConfig(Properties props) {

264

props.forEach((key, value) -> {

265

String keyStr = key.toString();

266

String valueStr = value.toString();

267

268

// Support comma-separated values

269

if (valueStr.contains(",")) {

270

String[] values = valueStr.split(",");

271

for (String v : values) {

272

properties.put(keyStr, v.trim());

273

}

274

} else {

275

properties.put(keyStr, valueStr);

276

}

277

});

278

}

279

280

public List<String> getPropertyValues(String key) {

281

return new ArrayList<>(properties.get(key));

282

}

283

284

public String getFirstPropertyValue(String key) {

285

Collection<String> values = properties.get(key);

286

return values.isEmpty() ? null : values.iterator().next();

287

}

288

289

public void addPropertyValue(String key, String value) {

290

properties.put(key, value);

291

}

292

293

public boolean hasProperty(String key) {

294

return properties.containsKey(key);

295

}

296

297

public boolean hasPropertyValue(String key, String value) {

298

return properties.containsMapping(key, value);

299

}

300

}

301

```

302

303

### Graph Adjacency Lists

304

305

```java { .api }

306

public class DirectedGraph<T> {

307

private final MultiValuedMap<T, T> adjacencyList = new HashSetValuedHashMap<>();

308

309

public void addEdge(T from, T to) {

310

adjacencyList.put(from, to);

311

}

312

313

public void removeEdge(T from, T to) {

314

adjacencyList.removeMapping(from, to);

315

}

316

317

public Set<T> getNeighbors(T vertex) {

318

return new HashSet<>(adjacencyList.get(vertex));

319

}

320

321

public Set<T> getAllVertices() {

322

Set<T> vertices = new HashSet<>(adjacencyList.keySet());

323

vertices.addAll(adjacencyList.values());

324

return vertices;

325

}

326

327

public int getOutDegree(T vertex) {

328

return adjacencyList.get(vertex).size();

329

}

330

331

public int getInDegree(T vertex) {

332

return (int) adjacencyList.entries().stream()

333

.filter(entry -> entry.getValue().equals(vertex))

334

.count();

335

}

336

337

public boolean hasPath(T from, T to) {

338

if (from.equals(to)) return true;

339

340

Set<T> visited = new HashSet<>();

341

Queue<T> queue = new LinkedList<>();

342

queue.offer(from);

343

visited.add(from);

344

345

while (!queue.isEmpty()) {

346

T current = queue.poll();

347

for (T neighbor : getNeighbors(current)) {

348

if (neighbor.equals(to)) return true;

349

if (!visited.contains(neighbor)) {

350

visited.add(neighbor);

351

queue.offer(neighbor);

352

}

353

}

354

}

355

return false;

356

}

357

}

358

```

359

360

### Inverted Index

361

362

```java { .api }

363

public class InvertedIndex {

364

private final MultiValuedMap<String, Document> termToDocuments = new HashSetValuedHashMap<>();

365

private final MultiValuedMap<Document, String> documentToTerms = new HashSetValuedHashMap<>();

366

367

public void addDocument(Document document) {

368

Set<String> terms = tokenize(document.getContent());

369

370

for (String term : terms) {

371

termToDocuments.put(term.toLowerCase(), document);

372

documentToTerms.put(document, term.toLowerCase());

373

}

374

}

375

376

public Set<Document> search(String term) {

377

return new HashSet<>(termToDocuments.get(term.toLowerCase()));

378

}

379

380

public Set<Document> searchMultiple(String... terms) {

381

if (terms.length == 0) return Collections.emptySet();

382

383

Set<Document> result = search(terms[0]);

384

for (int i = 1; i < terms.length; i++) {

385

result.retainAll(search(terms[i])); // Intersection

386

}

387

return result;

388

}

389

390

public Set<Document> searchAny(String... terms) {

391

Set<Document> result = new HashSet<>();

392

for (String term : terms) {

393

result.addAll(search(term)); // Union

394

}

395

return result;

396

}

397

398

public void removeDocument(Document document) {

399

Collection<String> terms = documentToTerms.remove(document);

400

for (String term : terms) {

401

termToDocuments.removeMapping(term, document);

402

}

403

}

404

405

private Set<String> tokenize(String content) {

406

// Simple tokenization - in practice, use proper text processing

407

return Arrays.stream(content.toLowerCase().split("\\W+"))

408

.filter(s -> !s.isEmpty())

409

.collect(Collectors.toSet());

410

}

411

}

412

```

413

414

## Utility Operations

415

416

### MultiMapUtils Class

417

418

```java { .api }

419

import org.apache.commons.collections4.MultiMapUtils;

420

421

// Create empty multimaps

422

MultiValuedMap<String, String> listMap = MultiMapUtils.newListValuedHashMap();

423

MultiValuedMap<String, String> setMap = MultiMapUtils.newSetValuedHashMap();

424

425

// Check if empty (null-safe)

426

boolean isEmpty = MultiMapUtils.isEmpty(null); // true

427

boolean isNotEmpty = MultiMapUtils.isEmpty(listMap); // false (depends on content)

428

429

// Create unmodifiable views

430

MultiValuedMap<String, String> unmodifiable = MultiMapUtils.unmodifiableMultiValuedMap(listMap);

431

432

// Create empty immutable multimap

433

MultiValuedMap<String, String> empty = MultiMapUtils.emptyMultiValuedMap();

434

435

// Transform multimaps

436

Transformer<String, String> upperCase = String::toUpperCase;

437

Transformer<String, String> addPrefix = s -> "prefix_" + s;

438

439

MultiValuedMap<String, String> transformed = MultiMapUtils.transformedMultiValuedMap(

440

listMap,

441

upperCase, // Key transformer

442

addPrefix // Value transformer

443

);

444

```

445

446

### Working with Map Views

447

448

```java { .api }

449

MultiValuedMap<String, Integer> scores = new ArrayListValuedHashMap<>();

450

scores.putAll("Alice", Arrays.asList(85, 92, 78));

451

scores.putAll("Bob", Arrays.asList(88, 76));

452

453

// Get as regular Map<K, Collection<V>>

454

Map<String, Collection<Integer>> asMap = scores.asMap();

455

456

// Modifications through map view affect original

457

asMap.get("Alice").add(95); // Adds to original multimap

458

Collection<Integer> bobScores = asMap.remove("Bob"); // Removes from original

459

460

// Create defensive copies

461

Map<String, List<Integer>> copyMap = new HashMap<>();

462

for (Map.Entry<String, Collection<Integer>> entry : asMap.entrySet()) {

463

copyMap.put(entry.getKey(), new ArrayList<>(entry.getValue()));

464

}

465

```

466

467

## Performance Considerations

468

469

### Implementation Choice Guidelines

470

471

```java { .api }

472

// Use ArrayListValuedHashMap when:

473

// - Order of values matters

474

// - Duplicates are needed

475

// - Values are accessed by index

476

ListValuedMap<String, String> orderedWithDuplicates = new ArrayListValuedHashMap<>();

477

478

// Use HashSetValuedHashMap when:

479

// - Uniqueness of values is required

480

// - Fast contains() operations needed

481

// - Order doesn't matter

482

SetValuedMap<String, String> uniqueValues = new HashSetValuedHashMap<>();

483

```

484

485

### Memory Usage

486

487

```java { .api }

488

// Memory efficient for sparse data (few keys, many values per key)

489

MultiValuedMap<String, String> sparse = new ArrayListValuedHashMap<>();

490

sparse.putAll("key1", Arrays.asList("v1", "v2", "v3", /* ... many values ... */));

491

492

// Less efficient for dense data (many keys, few values per key)

493

MultiValuedMap<String, String> dense = new ArrayListValuedHashMap<>();

494

for (int i = 0; i < 1000; i++) {

495

dense.put("key" + i, "value" + i); // Creates many small collections

496

}

497

498

// For dense data, consider regular Map<K, V> instead

499

Map<String, String> regularMap = new HashMap<>();

500

```

501

502

### Bulk Operations Performance

503

504

```java { .api }

505

MultiValuedMap<String, Integer> numbers = new ArrayListValuedHashMap<>();

506

507

// Efficient bulk addition

508

List<Integer> manyNumbers = IntStream.range(1, 1000).boxed().collect(Collectors.toList());

509

numbers.putAll("range", manyNumbers); // Single operation

510

511

// Less efficient individual additions

512

for (int i = 1; i < 1000; i++) {

513

numbers.put("range2", i); // Multiple operations, more overhead

514

}

515

516

// Efficient collection manipulation

517

Collection<Integer> existing = numbers.get("range");

518

existing.addAll(Arrays.asList(1001, 1002, 1003)); // Direct collection modification

519

```

520

521

## Thread Safety

522

523

MultiValuedMaps are not thread-safe by default. For concurrent access:

524

525

```java { .api }

526

// Option 1: External synchronization

527

MultiValuedMap<String, String> multiMap = new ArrayListValuedHashMap<>();

528

MultiValuedMap<String, String> syncMultiMap = MultiMapUtils.synchronizedMultiValuedMap(multiMap);

529

530

// Option 2: Custom concurrent wrapper

531

public class ConcurrentMultiValuedMap<K, V> {

532

private final MultiValuedMap<K, V> map = new ArrayListValuedHashMap<>();

533

private final ReadWriteLock lock = new ReentrantReadWriteLock();

534

535

public boolean put(K key, V value) {

536

lock.writeLock().lock();

537

try {

538

return map.put(key, value);

539

} finally {

540

lock.writeLock().unlock();

541

}

542

}

543

544

public Collection<V> get(K key) {

545

lock.readLock().lock();

546

try {

547

return new ArrayList<>(map.get(key)); // Defensive copy

548

} finally {

549

lock.readLock().unlock();

550

}

551

}

552

}

553

554

// Option 3: Use ConcurrentHashMap with CopyOnWriteArrayList

555

Map<String, Collection<String>> concurrent = new ConcurrentHashMap<>();

556

String key = "example";

557

concurrent.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add("value");

558

```

559

560

Multi-valued maps provide powerful abstractions for one-to-many relationships and are particularly useful for grouping, classification, and graph-like data structures. Choose between List and Set semantics based on whether you need ordering/duplicates or uniqueness guarantees.