问题
What do I mean with this? First let's look at some code I wrote:
let names = ['James', 'james', 'bob', 'JaMeS', 'Bob'];
let uNames = {};
names.forEach(n => {
let lower = n.toLowerCase();
if (!uNames[lower]) {
uNames[lower] = n;
}
});
names = Object.values(uNames);
console.log(names); // >>> (2) ["James", "bob"]
The goal here is to unique the given array case insensitive but keep one of the original inputs.
I was wondering if there is a more elegant/better performing solution to this problem than the one I came up with.
Just converting the whole array to lowercase before making it unique is not a solution, because I'd like the end result to consist only of values which were already in the input array. Which one (e.g. James
or james
or JaMeS
) is not relevant.
回答1:
I was wondering if there is a more elegant/better performing solution to this problem than the one I came up with.
Use a Map:
let names = ['James', 'james', 'bob', 'JaMeS', 'Bob'];
let uNames = new Map(names.map(s => [s.toLowerCase(), s]));
console.log([...uNames.values()]);
The constructor of Map
can take an array of pairs (nested arrays with 2 values: key and value). The Map will maintain a unique list of keys so while it is constructed previously stored values will get overwritten if the key is the same.
Once you have the Map, you can iterate over the values with .values()
.
With plain object
You could also use the Object.fromEntries method, which at the time of writing, is a stage 4 proposal (Draft ES2020) implemented in Chrome, Firefox, Opera and Safari:
let names = ['James', 'james', 'bob', 'JaMeS', 'Bob'];
let uNames = Object.fromEntries(names.map(s => [s.toLowerCase(), s]));
console.log(Object.values(uNames));
As you can see, the approach is quite similar.
First occurrence & Original order
The above will collect the last occurrence, in order of first occurrence.
In case you want to collect the first occurrence, you can just reverse the input first, and then continue as above. Then the output will have collected the first occurrence, in order of last occurrence.
In case you really need the first occurrences in order of first occurrence, you can use reduce
as follows:
let names = ['James', 'james', 'bob', 'JaMeS', 'Bob'];
let uNames = names.map(s => s.toLowerCase()).reduce((map, s, i) =>
map.get(s) ? map : map.set(s, names[i])
, new Map);
console.log([...uNames.values()]);
回答2:
If you want to deduplicate a string array, with priority given to the first occurrences, keeping the insertion order, use this.
const a = ["ALPHA", "10", "BETA", "10", "alpha", "beta", "aLphA"];
const b = ["3", "1", "2", "2", "3", "1"];
function dedupe(string_array) {
const entries = string_array
.slice()
.reverse()
.map((string, index) => [
string.toLowerCase(),
{
string,
index: string_array.length - 1 - index
}
]);
const case_insensitively_deduped_string_array = Array
.from((new Map(entries)).values())
.sort((a, b) => (a.index - b.index))
.map(item => item.string);
return case_insensitively_deduped_string_array;
// Takes the first occurrences, keeping the insertion order.
// Doesn’t modify the input array.
}
console.log(a, dedupe(a));
console.log(b, dedupe(b));
来源:https://stackoverflow.com/questions/48731396/javascript-unique-string-array-case-insensitive-but-keep-one-case-sensitive-resu