12

I am new in angular2 and typescript. I have a problem in creating an unique collection like Set<>. I want to avoid duplicate objects in a collection, for that purpose, try to use a set dataType like following code:

private cardItems = new Set<MyBean>([]);

MyBean is an object.

export class MyBean  {

  id:integer
  ownerId:integer
  ownerName:string
  img: string;

constructor() {

}
public equals(obj: MyBean) {
    console.log(obj.id);
    if (this.id == obj.id) {
        console.log(obj.id);
        return true;
    }
    if (obj == null)
        return false;

    return true;
}

public hashCode(obj: MyBean) {
    return obj.id
}

}

but equals and hashCode does not run in this way.and I have duplicate objects in set.

What is the solution for implementing Set?

Many thanks

3

4 Answers 4

7

How about extending the Set class and then overriding the add method:

interface SetItem {
    equals(other: SetItem): boolean;
}

class MySet<T extends SetItem> extends Set<T> {
    add(value: T): this {
        let found = false;
        this.forEach(item => {
            if (value.equals(item)) {
                found = true;
            }
        });

        if (!found) {
            super.add(value);
        }

        return this;
    }
}

(code in playground)

Sign up to request clarification or add additional context in comments.

2 Comments

"Add" method should work with O(1) or at least O(log n) This solutions works with O(n)
What about Set.has() ? Does it work with this?
2

What is the solution for implementing Set

The JavaScript Set uses the same algorithm as === and there is no way to override that in your JavaScript class.

Solution

You can use something that leverages an overridable function e.g. https://github.com/basarat/typescript-collections uses toString.

2 Comments

thanks for replying ,I have installed typescript-collections. but I want to distinguish my object by its id.Because of that, First added private recievers = new Collections.Set<MyBean>(); and after adding a new object like: this.recievers.add(contact); it contains <object object> dictionary. In java , we can add instance of an object . but I did not find some examples like that in typescript.Would ypu pleae introduce some samples for adding a class object into Set?
I add object into set collection by following code ,but I don't think it is a good way: this.recievers.add(new MyBean(contact.id, contact.firstName, contact.lastName, contact.mobile, contact.email)); I had to change MyBean constructor and consequently other parts need change.
1

You could use a technique similar to comparing when 2 objects are similar:

JSON.stringify(obj)

This would, however, require you to JSON.parse() the members of your Set upon read, and would require some extra typing.

const obj1 = {id: 1, data: 'blue'};
const obj2 = {id: 2, data: 'red'};
const obj3 = {id: 1, data: 'blue'};
const myStringifiedObjSet: Set<string> = new Set([]);

myStringifiedObjSet.add(JSON.stringify(obj1));
  // Set { '{"id":1,"data":"blue"}' }
myStringifiedObjSet.add(JSON.stringify(obj2));
  // Set { '{"id":1,"data":"blue"}', '{"id":2,"data":"red"}' }
myStringifiedObjSet.add(JSON.stringify(obj3));
  // Set { '{"id":1,"data":"blue"}', '{"id":2,"data":"red"}' }

myStringifiedObjSet.has(JSON.stringify(obj1));
  // true

const objArray: object[] = Array.from(myStringifiedObjSet).map(el => JSON.parse(el));
  // [ { id: 1, data: 'blue' }, { id: 2, data: 'red' } ]

Comments

0

A custom Set implementation should also implement other methods like has and it should preseve O(1) lookup and insertion time.

Using Set<string> with the JSON.stringify(obj) looses the original type.

Here is an alternative using fast-equals for deep equality and object-hash for O(1) lookup and insertion:

import { deepEqual } from 'fast-equals'
import objectHash, { type NotUndefined } from 'object-hash'

/**
 * TypeScript compares objects by reference, not by value.
 * The same behavior applies to Set, which is not unique by value.
 * This Set implementation uses value equality and preserves O(1) lookup and insertion time.
 */
class ValueSet<T extends NotUndefined> implements Set<T> {
  [Symbol.toStringTag]: string = 'ValueSet'

  private hashMap = new Map<string, T>()

  constructor(
    items: T[] = [],
    private toKey: (item: T) => string = objectHash,
  ) {
    for (const item of items) {
      this.add(item)
    }
  }

  get size(): number {
    return this.hashMap.size
  }

  has(item: T): boolean {
    const hash = this.toKey(item)
    const existingItem = this.hashMap.get(hash)
    return deepEqual(item, existingItem)
  }

  add(item: T): this {
    const hash = this.toKey(item)

    if (!this.hashMap.has(hash)) {
      this.hashMap.set(hash, item)
      return this
    }

    const existingItem = this.hashMap.get(hash)
    if (!deepEqual(item, existingItem)) {
      this.hashMap.set(hash, item)
    }
    return this
  }

  delete(item: T): boolean {
    const hash = this.toKey(item)
    const existingItem = this.hashMap.get(hash)
    if (deepEqual(item, existingItem)) {
      return this.hashMap.delete(hash)
    }
    return false
  }

  clear(): void {
    this.hashMap.clear()
  }

  forEach(
    callbackfn: (value: T, value2: T, set: Set<T>) => void,
    thisArg?: unknown,
  ): void {
    this.hashMap.forEach(value => {
      callbackfn.call(thisArg, value, value, this)
    })
  }

  [Symbol.iterator](): SetIterator<T> {
    return this.hashMap.values()
  }

  keys(): SetIterator<T> {
    // Keys and values of a Set are the same thing.
    return this.hashMap.values()
  }

  values(): SetIterator<T> {
    return this.hashMap.values()
  }

  entries(): SetIterator<[T, T]> {
    return new Set(this.hashMap.values()).entries()
  }
}

export default ValueSet

And here are some tests for it using vitest (same syntax as jest):

import { describe, expect, it } from 'vitest'
import ValueSet from '../ValueSet'

interface SomeType {
  foo: number
}

describe('ValueSet', () => {
  describe('constructor', () => {
    it('initializes the set with items', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }, { foo: 1 }])
      expect(set.size).toBe(2)
    })
  })

  describe('size', () => {
    it('returns the current number of items', () => {
      const set = new ValueSet<SomeType>()
      expect(set.size).toBe(0)

      set.add({ foo: 0 })
      expect(set.size).toBe(1)

      set.delete({ foo: 0 })
      expect(set.size).toBe(0)
    })
  })

  describe('has', () => {
    it('returns true for existing items', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }])

      expect(set.has({ foo: 0 })).toBe(true)
    })
    it('returns false for non-existent items', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }])

      expect(set.has({ foo: 1 })).toBe(false)
    })
  })

  describe('add', () => {
    it('adds unique items to the set', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }])
      set.add({ foo: 1 })

      expect(set.size).toBe(2)
      expect(set.has({ foo: 0 })).toBe(true)
      expect(set.has({ foo: 1 })).toBe(true)
    })

    it('ignores duplicate items', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }])
      set.add({ foo: 0 })

      expect(set.size).toBe(1)
      expect(set.has({ foo: 0 })).toBe(true)
    })

    it('overwrites items with the same hash/key but different value', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }], () => 'constant_hash')
      set.add({ foo: 1 })

      expect(set.size).toBe(1)
      expect(set.has({ foo: 0 })).toBe(false)
      expect(set.has({ foo: 1 })).toBe(true)
    })
  })

  describe('delete', () => {
    it('deletes existing items', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }, { foo: 1 }])

      expect(set.delete({ foo: 0 })).toBe(true)
      expect(set.size).toBe(1)
      expect(set.has({ foo: 0 })).toBe(false)
      expect(set.has({ foo: 1 })).toBe(true)
    })

    it('ignores non-existent items', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }])

      expect(set.delete({ foo: 1 })).toBe(false)
      expect(set.size).toBe(1)
      expect(set.has({ foo: 0 })).toBe(true)
    })
  })

  describe('clear', () => {
    it('removes all items from the set', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }, { foo: 1 }])
      expect(set.size).toBe(2)
      set.clear()
      expect(set.size).toBe(0)
    })
  })

  describe('forEach', () => {
    it('preserves the order of insertion', () => {
      const set = new ValueSet<SomeType>()
      set.add({ foo: 0 }).add({ foo: 2 }).add({ foo: 1 })

      const items: SomeType[] = []
      set.forEach(value => {
        items.push(value)
      })

      expect(items).toEqual([{ foo: 0 }, { foo: 2 }, { foo: 1 }])
    })
  })

  describe('for-of', () => {
    it('preserves the order of insertion', () => {
      const set = new ValueSet<SomeType>()
      set.add({ foo: 0 }).add({ foo: 2 }).add({ foo: 1 })

      const items: SomeType[] = []
      for (const item of set) {
        items.push(item)
      }

      expect(items).toEqual([{ foo: 0 }, { foo: 2 }, { foo: 1 }])
    })
  })

  describe('keys', () => {
    it('returns all items', () => {
      const items = [{ foo: 0 }, { foo: 1 }]
      const set = new ValueSet<SomeType>(items)

      expect(Array.from(set.keys())).toEqual(items)
    })
  })

  describe('values', () => {
    it('returns all items', () => {
      const items = [{ foo: 0 }, { foo: 1 }]
      const set = new ValueSet<SomeType>(items)

      expect(Array.from(set.values())).toEqual(items)
    })
  })

  describe('entries', () => {
    it('returns the entries with the item as key and value', () => {
      const set = new ValueSet<SomeType>([{ foo: 0 }, { foo: 1 }])

      expect(Array.from(set.entries())).toEqual([
        [{ foo: 0 }, { foo: 0 }],
        [{ foo: 1 }, { foo: 1 }],
      ])
    })
  })
})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.