Home > Mobile >  Generic Typescript function to check for duplicates in an array
Generic Typescript function to check for duplicates in an array

Time:09-16

I'm trying make a generic Typescript function to check if an array contains duplicates. For example:

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'}
];

That is the data.

This is what I want to do with the data:

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
};

Regarding the checkDuplicate(), I want to make it a generic function, but I'm struggling with the logic.

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')
  };
};

It's a little bit complex and I haven't been able to figure out the logic to work with typescript. Any advice on how I can implement this would be appreciated!

CodePudding user response:

One reasonable way to approach this is to have checkDuplicate() take a single array items of generic type T[], and another array keysToCheck of type K[], where K is a keylike type (or union of keylike types) and where T is a type with keys in K and whose values at those keys are strings. That is, the call signature of checkDuplicate() should be

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

This function should iterate over both items and keysToCheck, and if it finds an item where a property is the same string as the same property in a previous item, it should throw an error.

If you had such a function, you could write the version which accepts students and existingState, two arrays of Student objects, like this:

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

where we are spreading the students and existingState arrays into a single array to pass as items to checkDuplicate(), and since we are checking Student we are passing ["name", "class"] as keysToCheck.


Here's a possible implementation of 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);
        }
    }
}

The way it works is that we create an object named vals with one key for each element key of keysToCheck. Each element vals[key] is the Set of strings we have already seen for that key. Every time we see a string-valued property val with key key in any item in the items array, we check whether the set in vals[key] has val. If so, we've seen this value for this key before, so we throw an error. If not, we add it to the set.

(Note that it's possible to replace Set<string> with a plain object of the form Record<string, true | undefined>, as shown in Mimicking sets in JavaScript? , but I'm using Set here for clarity.)


Okay, let's test it against your example:

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

Looks good. It throws an error at runtime and properly identifies the duplicate data.

Playground link to code

  • Related