用于检查数组中重复项的通用 Typescript 函数

Generic Typescript function to check for duplicates in an array

我正在尝试创建一个通用的 Typescript 函数来检查数组是否包含重复项。例如:

interface Student {
  name: string;
  class: string;
};

const students: Student[] = [
  { name: 'John Smith', class: 'Science' },
  { name: 'Edward Ryan', class: 'Math' },
  { name: 'Jessica Li', class: 'Social Studies'},
  { name: 'John Smith', class: 'English'}
];

这就是数据。

这就是我要对数据执行的操作:

const registerStudents = async (students: Student[]): Promise<void> {
  
  checkDuplicate(students, existingState); //This is the function I want to build

  const response = await axios.post('/students/new', students)
  existingState.push(response); //pushes newly registers students to the existing state
};

关于checkDuplicate(),我想让它成为一个通用函数,但我在逻辑上苦苦挣扎。

export const checkDuplicate = <T>(items: T[], existingState: T[]): void {
  //checks if the items have any duplicate names, in this case, it would be 'John Smith', and if so, throw an error

  //Also checks if items have any duplicate names with the existingState of the application, and if so, throw an error

  if (duplicate) {
    throw new Error('contains identical information')
  };
};

它有点复杂,我一直无法弄清楚使用打字稿的逻辑。任何关于我如何实现它的建议都将不胜感激!

一个合理的方法是让 checkDuplicate() 接受一个通用类型 T[] 的数组 items 和另一个类型 [=18] 的数组 keysToCheck =],其中 K 是类似键的类型(或 union 的类似键类型),其中 T 是具有 K 中的键的类型,并且这些键的值为 strings。即checkDuplicate()的调用签名应该是

declare const checkDuplicate: <T extends Record<K, string>, K extends PropertyKey>(
    items: T[],
    keysToCheck: K[]
) => void;

此函数应遍历 itemskeysToCheck,如果它找到一个项目,其中 属性 与 a 中的相同 属性 是相同的字符串上一项,它应该会引发错误。

如果你有这样的功能,你可以编写接受 studentsexistingState 的版本,两个 Student 对象数组,如下所示:

function checkDuplicateStudents(students: Student[], existingState: Student[]) {
    checkDuplicate([...students, ...existingState], ["name", "class"]);
}

我们 spreadingstudentsexistingState 数组合并为一个数组,作为 items 传递给 checkDuplicate(),因为我们正在检查Student 我们将 ["name", "class"] 作为 keysToCheck.


这是 checkDuplicate() 的可能实现:

const checkDuplicate = <T extends Record<K, string>, K extends PropertyKey>(
    items: T[],
    keysToCheck: K[]
): void => {
    const vals = {} as Record<K, Set<string>>;
    keysToCheck.forEach(key => vals[key] = new Set());
    for (let item of items) {
        for (let key of keysToCheck) {
            const val: string = item[key];
            const valSet: Set<string> = vals[key]
            if (valSet.has(val)) {
                throw new Error(
                    'contains identical information at key "' +
                    key + '" with value "' + val + '"');
            };
            valSet.add(val);
        }
    }
}

它的工作方式是我们创建一个名为 vals 的对象,并为 keysToCheck 的每个元素 key 创建一个键。每个元素 vals[key] 都是我们已经看到的 key 字符串的 Set。每次我们在 items 数组中的任何 item 中看到带有键 keystring 值 属性 val 时,我们检查集合是否在 vals[key] 中有 val。如果是这样,我们之前已经看到这个键的这个值,所以我们抛出一个错误。如果没有,我们将其添加到集合中。

(请注意,可以用 Record<string, true | undefined> 形式的普通对象替换 Set<string>,如 Mimicking sets in JavaScript? 所示,但我在这里使用 Set为清楚起见。)


好的,让我们根据您的示例进行测试:

checkDuplicateStudents(students, []);
// contains identical information at key "name" with value "John Smith"

看起来不错。它会在运行时抛出错误并正确识别重复数据。

Playground link to code