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

bidimaps.mddocs/

0

# Bidirectional Maps (BidiMaps)

1

2

Bidirectional maps allow efficient lookup in both directions - from key to value and from value to key. They maintain 1:1 relationships between keys and values, ensuring that both keys and values are unique within the map.

3

4

## Core Interfaces

5

6

### BidiMap<K, V> Interface

7

8

The primary interface for bidirectional maps.

9

10

```java { .api }

11

import org.apache.commons.collections4.BidiMap;

12

import org.apache.commons.collections4.bidimap.DualHashBidiMap;

13

14

BidiMap<String, Integer> bidiMap = new DualHashBidiMap<>();

15

16

// Standard map operations (key -> value)

17

bidiMap.put("one", 1);

18

bidiMap.put("two", 2);

19

bidiMap.put("three", 3);

20

21

Integer value = bidiMap.get("two"); // Returns 2

22

23

// Reverse lookup operations (value -> key)

24

String key = bidiMap.getKey(2); // Returns "two"

25

String removedKey = bidiMap.removeValue(3); // Removes and returns "three"

26

27

// Get inverse view

28

BidiMap<Integer, String> inverse = bidiMap.inverseBidiMap();

29

String keyFromInverse = inverse.get(1); // Returns "one"

30

31

// Both views reflect the same underlying data

32

bidiMap.put("four", 4);

33

Integer newValue = inverse.get("four"); // Returns 4

34

```

35

36

### OrderedBidiMap<K, V> Interface

37

38

A bidirectional map that maintains insertion order.

39

40

```java { .api }

41

import org.apache.commons.collections4.OrderedBidiMap;

42

import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;

43

import org.apache.commons.collections4.OrderedMapIterator;

44

45

OrderedBidiMap<String, Integer> orderedBidi = new DualLinkedHashBidiMap<>();

46

orderedBidi.put("first", 1);

47

orderedBidi.put("second", 2);

48

orderedBidi.put("third", 3);

49

50

// Navigate in insertion order

51

String firstKey = orderedBidi.firstKey(); // Returns "first"

52

String lastKey = orderedBidi.lastKey(); // Returns "third"

53

String nextKey = orderedBidi.nextKey("first"); // Returns "second"

54

String prevKey = orderedBidi.previousKey("third"); // Returns "second"

55

56

// Ordered iteration

57

OrderedMapIterator<String, Integer> iterator = orderedBidi.mapIterator();

58

while (iterator.hasNext()) {

59

String key = iterator.next();

60

Integer value = iterator.getValue();

61

System.out.println(key + " -> " + value);

62

}

63

```

64

65

### SortedBidiMap<K, V> Interface

66

67

A bidirectional map that maintains elements in sorted order.

68

69

```java { .api }

70

import org.apache.commons.collections4.SortedBidiMap;

71

import org.apache.commons.collections4.bidimap.DualTreeBidiMap;

72

import java.util.Comparator;

73

74

// Natural ordering

75

SortedBidiMap<String, Integer> sortedBidi = new DualTreeBidiMap<>();

76

sortedBidi.put("zebra", 26);

77

sortedBidi.put("alpha", 1);

78

sortedBidi.put("beta", 2);

79

80

// Elements are maintained in sorted order

81

String firstKey = sortedBidi.firstKey(); // Returns "alpha"

82

String lastKey = sortedBidi.lastKey(); // Returns "zebra"

83

84

// Custom comparators for keys and values

85

Comparator<String> keyComp = Comparator.reverseOrder();

86

Comparator<Integer> valueComp = Comparator.naturalOrder();

87

SortedBidiMap<String, Integer> customSorted = new DualTreeBidiMap<>(keyComp, valueComp);

88

```

89

90

## Concrete Implementations

91

92

### DualHashBidiMap<K, V>

93

94

Hash-based implementation using two HashMap instances for O(1) performance.

95

96

```java { .api }

97

import org.apache.commons.collections4.bidimap.DualHashBidiMap;

98

import java.util.Map;

99

import java.util.HashMap;

100

101

// Create empty bidirectional map

102

DualHashBidiMap<String, String> countryCapital = new DualHashBidiMap<>();

103

104

// Populate with data

105

countryCapital.put("USA", "Washington");

106

countryCapital.put("France", "Paris");

107

countryCapital.put("Japan", "Tokyo");

108

countryCapital.put("Germany", "Berlin");

109

110

// Efficient bidirectional lookups

111

String capital = countryCapital.get("France"); // "Paris"

112

String country = countryCapital.getKey("Tokyo"); // "Japan"

113

114

// Create from existing map

115

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

116

existingMap.put("Italy", "Rome");

117

existingMap.put("Spain", "Madrid");

118

DualHashBidiMap<String, String> fromMap = new DualHashBidiMap<>(existingMap);

119

120

// Duplicate value detection

121

try {

122

countryCapital.put("UK", "Paris"); // Throws IllegalArgumentException

123

} catch (IllegalArgumentException e) {

124

System.out.println("Paris is already mapped to France");

125

}

126

```

127

128

### DualLinkedHashBidiMap<K, V>

129

130

LinkedHashMap-based implementation that maintains insertion order.

131

132

```java { .api }

133

import org.apache.commons.collections4.bidimap.DualLinkedHashBidiMap;

134

135

DualLinkedHashBidiMap<Integer, String> priorities = new DualLinkedHashBidiMap<>();

136

137

// Add in specific order

138

priorities.put(1, "High");

139

priorities.put(2, "Medium");

140

priorities.put(3, "Low");

141

142

// Maintains insertion order during iteration

143

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

144

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

145

}

146

// Output: Priority 1: High, Priority 2: Medium, Priority 3: Low

147

148

// Inverse also maintains corresponding order

149

BidiMap<String, Integer> inversePriorities = priorities.inverseBidiMap();

150

for (String level : inversePriorities.keySet()) {

151

System.out.println(level + " has priority " + inversePriorities.get(level));

152

}

153

// Output: High has priority 1, Medium has priority 2, Low has priority 3

154

```

155

156

### DualTreeBidiMap<K, V>

157

158

TreeMap-based implementation that maintains sorted order for both keys and values.

159

160

```java { .api }

161

import org.apache.commons.collections4.bidimap.DualTreeBidiMap;

162

import java.util.Comparator;

163

164

// Natural ordering for both keys and values

165

DualTreeBidiMap<String, Integer> grades = new DualTreeBidiMap<>();

166

grades.put("Alice", 95);

167

grades.put("Bob", 87);

168

grades.put("Charlie", 92);

169

170

// Keys sorted alphabetically

171

System.out.println("First student: " + grades.firstKey()); // "Alice"

172

System.out.println("Last student: " + grades.lastKey()); // "Charlie"

173

174

// Values also sorted in inverse map

175

SortedBidiMap<Integer, String> byGrades = grades.inverseBidiMap();

176

System.out.println("Lowest grade: " + byGrades.firstKey()); // 87

177

System.out.println("Highest grade: " + byGrades.lastKey()); // 95

178

179

// Custom comparators

180

Comparator<String> nameComp = (a, b) -> a.length() - b.length(); // By name length

181

Comparator<Integer> gradeComp = Comparator.reverseOrder(); // Descending grades

182

DualTreeBidiMap<String, Integer> customOrder = new DualTreeBidiMap<>(nameComp, gradeComp);

183

```

184

185

### TreeBidiMap<K, V>

186

187

Red-black tree implementation for Comparable objects with excellent performance.

188

189

```java { .api }

190

import org.apache.commons.collections4.bidimap.TreeBidiMap;

191

import java.time.LocalDate;

192

import java.util.Map;

193

import java.util.HashMap;

194

195

// For Comparable types

196

TreeBidiMap<LocalDate, String> events = new TreeBidiMap<>();

197

events.put(LocalDate.of(2024, 1, 1), "New Year");

198

events.put(LocalDate.of(2024, 7, 4), "Independence Day");

199

events.put(LocalDate.of(2024, 12, 25), "Christmas");

200

201

// Sorted by date

202

LocalDate firstDate = events.firstKey(); // 2024-01-01

203

LocalDate lastDate = events.lastKey(); // 2024-12-25

204

205

// Create from existing map

206

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

207

scoreMap.put("Alpha", 100);

208

scoreMap.put("Beta", 85);

209

scoreMap.put("Gamma", 92);

210

TreeBidiMap<String, Integer> scores = new TreeBidiMap<>(scoreMap);

211

```

212

213

## Bidirectional Map Operations

214

215

### Unique Key-Value Constraints

216

217

BidiMaps enforce 1:1 relationships between keys and values.

218

219

```java { .api }

220

BidiMap<String, String> usernames = new DualHashBidiMap<>();

221

222

// Initial mapping

223

usernames.put("john.doe", "John Doe");

224

usernames.put("jane.smith", "Jane Smith");

225

226

// Attempting to add duplicate value removes existing mapping

227

String oldKey = usernames.put("j.doe", "John Doe"); // oldKey is null

228

// Now "John Doe" maps to "j.doe", "john.doe" mapping is removed

229

230

System.out.println(usernames.getKey("John Doe")); // Returns "j.doe"

231

System.out.println(usernames.get("john.doe")); // Returns null

232

233

// Using putAll with conflicting values

234

Map<String, String> newMappings = Map.of(

235

"admin", "Administrator",

236

"jane.doe", "Jane Smith" // Conflicts with existing value

237

);

238

usernames.putAll(newMappings);

239

// "Jane Smith" now maps to "jane.doe", "jane.smith" is removed

240

```

241

242

### Inverse Map Views

243

244

Inverse maps provide a flipped view of the same underlying data.

245

246

```java { .api }

247

BidiMap<String, Integer> original = new DualHashBidiMap<>();

248

original.put("A", 1);

249

original.put("B", 2);

250

251

BidiMap<Integer, String> inverse = original.inverseBidiMap();

252

253

// Both views share the same data

254

System.out.println(original.size()); // 2

255

System.out.println(inverse.size()); // 2

256

257

// Modifications through inverse affect original

258

inverse.put(3, "C");

259

System.out.println(original.get("C")); // Returns 3

260

261

// Removing through inverse

262

String removed = inverse.remove(2); // Returns "B"

263

System.out.println(original.containsKey("B")); // false

264

265

// Getting inverse of inverse returns original

266

BidiMap<String, Integer> doubleInverse = inverse.inverseBidiMap();

267

System.out.println(doubleInverse == original); // true

268

```

269

270

### MapIterator Usage

271

272

Efficient iteration over bidirectional maps.

273

274

```java { .api }

275

import org.apache.commons.collections4.MapIterator;

276

277

BidiMap<String, Double> stockPrices = new DualHashBidiMap<>();

278

stockPrices.put("AAPL", 150.25);

279

stockPrices.put("GOOGL", 2750.80);

280

stockPrices.put("MSFT", 305.15);

281

282

// Iterate with MapIterator

283

MapIterator<String, Double> iterator = stockPrices.mapIterator();

284

while (iterator.hasNext()) {

285

String symbol = iterator.next();

286

Double price = iterator.getValue();

287

288

// Update prices with 5% increase

289

iterator.setValue(price * 1.05);

290

291

System.out.println(symbol + ": $" + iterator.getValue());

292

}

293

294

// Iterate inverse map

295

MapIterator<Double, String> priceIterator = stockPrices.inverseBidiMap().mapIterator();

296

while (priceIterator.hasNext()) {

297

Double price = priceIterator.next();

298

String symbol = priceIterator.getValue();

299

System.out.println("$" + price + " -> " + symbol);

300

}

301

```

302

303

## Advanced Usage Patterns

304

305

### Caching and Lookup Tables

306

307

```java { .api }

308

public class UserManager {

309

private final BidiMap<String, Long> usernameToId = new DualHashBidiMap<>();

310

311

public void registerUser(String username, Long userId) {

312

// BidiMap ensures both username and userId are unique

313

usernameToId.put(username, userId);

314

}

315

316

public Long getUserId(String username) {

317

return usernameToId.get(username);

318

}

319

320

public String getUsername(Long userId) {

321

return usernameToId.getKey(userId);

322

}

323

324

public boolean isUsernameTaken(String username) {

325

return usernameToId.containsKey(username);

326

}

327

328

public boolean isUserIdTaken(Long userId) {

329

return usernameToId.containsValue(userId);

330

}

331

332

public void deleteUser(String username) {

333

usernameToId.remove(username);

334

}

335

336

public void deleteUserById(Long userId) {

337

usernameToId.removeValue(userId);

338

}

339

}

340

```

341

342

### State Machine Mappings

343

344

```java { .api }

345

public enum OrderState { PENDING, CONFIRMED, SHIPPED, DELIVERED, CANCELLED }

346

public enum StatusCode { 100, 200, 300, 400, 500 }

347

348

public class OrderStateMachine {

349

private final BidiMap<OrderState, StatusCode> stateCodes = new DualHashBidiMap<>();

350

351

public OrderStateMachine() {

352

stateCodes.put(OrderState.PENDING, StatusCode.100);

353

stateCodes.put(OrderState.CONFIRMED, StatusCode.200);

354

stateCodes.put(OrderState.SHIPPED, StatusCode.300);

355

stateCodes.put(OrderState.DELIVERED, StatusCode.400);

356

stateCodes.put(OrderState.CANCELLED, StatusCode.500);

357

}

358

359

public StatusCode getStatusCode(OrderState state) {

360

return stateCodes.get(state);

361

}

362

363

public OrderState getState(StatusCode code) {

364

return stateCodes.getKey(code);

365

}

366

367

public Set<OrderState> getAllStates() {

368

return stateCodes.keySet();

369

}

370

371

public Set<StatusCode> getAllCodes() {

372

return stateCodes.values();

373

}

374

}

375

```

376

377

### Configuration Management

378

379

```java { .api }

380

public class ConfigurationManager {

381

private final OrderedBidiMap<String, String> properties = new DualLinkedHashBidiMap<>();

382

383

public void loadConfiguration(Properties props) {

384

props.forEach((key, value) -> properties.put(key.toString(), value.toString()));

385

}

386

387

public String getProperty(String key) {

388

return properties.get(key);

389

}

390

391

public String getPropertyKey(String value) {

392

return properties.getKey(value);

393

}

394

395

public void setProperty(String key, String value) {

396

properties.put(key, value);

397

}

398

399

// Maintain insertion order for configuration display

400

public Map<String, String> getAllProperties() {

401

return new LinkedHashMap<>(properties);

402

}

403

404

// Find all keys with a specific value pattern

405

public Set<String> getKeysForValue(String valuePattern) {

406

return properties.entrySet().stream()

407

.filter(entry -> entry.getValue().matches(valuePattern))

408

.map(Map.Entry::getKey)

409

.collect(Collectors.toSet());

410

}

411

}

412

```

413

414

## Performance Characteristics

415

416

### Implementation Comparison

417

418

| Implementation | Key Lookup | Value Lookup | Ordering | Memory Overhead |

419

|---------------|------------|--------------|----------|-----------------|

420

| DualHashBidiMap | O(1) | O(1) | None | Low |

421

| DualLinkedHashBidiMap | O(1) | O(1) | Insertion | Medium |

422

| DualTreeBidiMap | O(log n) | O(log n) | Sorted | Medium |

423

| TreeBidiMap | O(log n) | O(log n) | Natural | Low |

424

425

### Memory Considerations

426

427

```java { .api }

428

// DualHashBidiMap uses two HashMap instances

429

BidiMap<String, Integer> hash = new DualHashBidiMap<>();

430

// Memory: ~2x HashMap overhead + entry objects

431

432

// TreeBidiMap uses single tree structure

433

BidiMap<String, Integer> tree = new TreeBidiMap<>();

434

// Memory: ~1.5x TreeMap overhead + navigation pointers

435

436

// For memory-critical applications with large datasets

437

BidiMap<Integer, Integer> compact = new TreeBidiMap<>(); // More memory efficient

438

BidiMap<String, String> spacious = new DualHashBidiMap<>(); // Faster but more memory

439

```

440

441

### Thread Safety

442

443

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

444

445

```java { .api }

446

// Option 1: External synchronization

447

BidiMap<String, Integer> bidiMap = new DualHashBidiMap<>();

448

Map<String, Integer> syncMap = Collections.synchronizedMap(bidiMap);

449

450

// Option 2: Concurrent wrapper (custom implementation needed)

451

public class ConcurrentBidiMap<K, V> {

452

private final BidiMap<K, V> map;

453

private final ReadWriteLock lock = new ReentrantReadWriteLock();

454

455

public ConcurrentBidiMap(BidiMap<K, V> map) {

456

this.map = map;

457

}

458

459

public V get(K key) {

460

lock.readLock().lock();

461

try {

462

return map.get(key);

463

} finally {

464

lock.readLock().unlock();

465

}

466

}

467

468

public K getKey(V value) {

469

lock.readLock().lock();

470

try {

471

return map.getKey(value);

472

} finally {

473

lock.readLock().unlock();

474

}

475

}

476

477

public V put(K key, V value) {

478

lock.writeLock().lock();

479

try {

480

return map.put(key, value);

481

} finally {

482

lock.writeLock().unlock();

483

}

484

}

485

}

486

```

487

488

## Best Practices

489

490

### Choosing the Right Implementation

491

492

```java { .api }

493

// Fast lookups, no ordering requirements

494

BidiMap<String, Integer> fast = new DualHashBidiMap<>();

495

496

// Need to maintain insertion order

497

BidiMap<String, Integer> ordered = new DualLinkedHashBidiMap<>();

498

499

// Need sorted iteration, have Comparable keys/values

500

BidiMap<String, Integer> sorted = new DualTreeBidiMap<>();

501

502

// Memory efficient for Comparable types

503

BidiMap<String, Integer> efficient = new TreeBidiMap<>();

504

```

505

506

### Error Handling

507

508

```java { .api }

509

BidiMap<String, String> errorProne = new DualHashBidiMap<>();

510

511

try {

512

errorProne.put("key1", "value1");

513

errorProne.put("key2", "value1"); // May remove key1 mapping

514

} catch (Exception e) {

515

// Handle potential issues

516

}

517

518

// Safer approach - check before inserting

519

public boolean safePut(BidiMap<String, String> map, String key, String value) {

520

if (map.containsValue(value)) {

521

String existingKey = map.getKey(value);

522

if (!key.equals(existingKey)) {

523

System.out.println("Warning: Value '" + value + "' already mapped to '" + existingKey + "'");

524

return false;

525

}

526

}

527

map.put(key, value);

528

return true;

529

}

530

```

531

532

Bidirectional maps provide efficient two-way lookup capabilities while maintaining data consistency through 1:1 key-value relationships. Choose the appropriate implementation based on your performance requirements and ordering needs.