如何高效地将区域敏感字符串用作数据结构的键?
Great question! Let's break this down into two clear parts: first, building a string normalization function that groups strings your locale comparison treats as equal, and second, finding or creating an ordered TreeMap that supports custom comparison logic.
JavaScript's native Map and object keys rely on strict equality (===), which doesn't align with the locale-aware equality from a.localeCompare(b, locale, { sensitivity: 'base' }) (e.g., Café and cafe would be considered equal by your comparator but aren't strictly equal).
The solution here is to use the Intl.Collator API, which provides a getSortKey() method. This method returns a Uint8Array that's identical for all strings your comparator deems equal. You can convert this array into a string (or a frozen array) to use as a key in a Map or object.
Here's a concrete example:
// Initialize your collator with the desired locale and settings const collator = new Intl.Collator('en-US', { sensitivity: 'base' }); // Replace 'en-US' with your target locale // Normalization function function normalizeForCollation(str) { const sortKey = collator.getSortKey(str); // Convert the Uint8Array to a string that can be used as a Map key return Array.from(sortKey).join(','); // Alternative: Use base64 for a shorter string (good for large datasets) // return btoa(String.fromCharCode(...sortKey)); } // Test it out const str1 = 'Café'; const str2 = 'cafe'; const str3 = 'Apple'; console.log(collator.compare(str1, str2)); // 0 (equal per locale rules) console.log(normalizeForCollation(str1) === normalizeForCollation(str2)); // true console.log(normalizeForCollation(str1) === normalizeForCollation(str3)); // false
You can then use this normalized string as a key in a Map:
const myMap = new Map(); myMap.set(normalizeForCollation(str1), 'Shared value for collation-equal strings'); console.log(myMap.get(normalizeForCollation(str2))); // 'Shared value for collation-equal strings'
JavaScript doesn't have a built-in TreeMap, but there are solid third-party options, and you can also roll your own if you prefer no dependencies.
Third-Party Libraries
collections TreeMap
The collections npm package includes a TreeMap that supports custom comparison functions directly. This is probably the easiest out-of-the-box solution:
const { TreeMap } = require('collections'); // Your custom locale-aware comparator const localeComparator = (a, b) => a.localeCompare(b, 'en-US', { sensitivity: 'base' }); // Create the TreeMap with your comparator const sortedTreeMap = new TreeMap(localeComparator); // Add entries (duplicates per comparator will overwrite existing ones) sortedTreeMap.set('Café', 1); sortedTreeMap.set('cafe', 2); // Overwrites the first entry since they're equal sortedTreeMap.set('Banana', 3); sortedTreeMap.set('Apple', 4); // Iterate in sorted order (matches your locale rules) for (const [key, value] of sortedTreeMap) { console.log(`${key}: ${value}`); // Output (en-US locale): Apple: 4, Banana: 3, Café: 2 }
Other Lightweight Options
If you want something smaller, libraries like redblack (a red-black tree implementation) or sorted-array let you define custom comparators. These are good choices if you don't need the full feature set of collections.
Roll Your Own TreeMap
If you want to avoid dependencies, you can implement a basic balanced binary tree (like a red-black tree or AVL tree) that uses your custom comparator. The core idea is to replace all default equality/sorting checks with your localeCompare logic.
Here's a minimal framework to get you started:
class CustomTreeMap { constructor(compareFn) { this.compareFn = compareFn; this.root = null; } // Simplified insert logic (you'd need to add balancing for efficiency) set(key, value) { if (!this.root) { this.root = { key, value, left: null, right: null }; return; } let current = this.root; while (true) { const comparison = this.compareFn(key, current.key); if (comparison === 0) { current.value = value; // Overwrite existing key return; } else if (comparison < 0) { if (!current.left) { current.left = { key, value, left: null, right: null }; return; } current = current.left; } else { if (!current.right) { current.right = { key, value, left: null, right: null }; return; } current = current.right; } } } // Simplified get logic get(key) { let current = this.root; while (current) { const comparison = this.compareFn(key, current.key); if (comparison === 0) return current.value; current = comparison < 0 ? current.left : current.right; } return undefined; } // In-order traversal for sorted iteration *[Symbol.iterator]() { function* traverse(node) { if (node.left) yield* traverse(node.left); yield [node.key, node.value]; if (node.right) yield* traverse(node.right); } yield* traverse(this.root); } } // Usage const myTree = new CustomTreeMap(localeComparator); myTree.set('Café', 1); myTree.set('Apple', 2); for (const entry of myTree) console.log(entry); // ['Apple', 2], ['Café', 1]
- Use the normalization approach if you just need to group collation-equal keys in a hash map.
- Use a TreeMap (third-party or custom) if you need ordered operations like sorted iteration, finding the nearest key, or range queries.
内容的提问来源于stack exchange,提问作者Conor Murphy

