Skip to content

Commit

Permalink
EqualityComaprer for collections (Dictionary, HashSet) (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
vdolek committed Oct 6, 2020
1 parent db575f0 commit cf584f3
Show file tree
Hide file tree
Showing 56 changed files with 1,019 additions and 169 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ singleOrDefault, join, where etc.).

- All .NET LINQ methods available and some more
- Deferred (lazy) execution
- Dictionary and HashSet with EqualityComparer support (same as in .NET)
- Intellisense friendly
- Implemented using generators and iterators
- ForOf cycle compatible (with `downlevelIteration` TS option set to `true`)
- ForOf cycle compatible
- Supports ES5 targeting

## Playground
Expand Down Expand Up @@ -51,7 +52,7 @@ const enumerable = Enumerable.from(data); // or List.from(data)
const adults = enumerable.where(x => x.age >= 18);
const adultsGroupedBySex = adults.groupBy(x => x.sex); // nothing is executed so far

// use any collection (Enumerable, List, ...) in ForOf cycle
// use any collection (Enumerable, List, Dictionary, ...) in ForOf cycle
for (const group of adultsGroupedBySex) {
console.debug(`Sex: ${group.key}, count: ${group.count()}`);
}
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "sharp-collections",
"version": "1.3.0",
"version": "1.4.0",
"description": ".NET Linq like collection library for TypeScript and JavaScript.",
"scripts": {
"build": "tsc",
"lint": "tslint --project .",
"prepublish": "npm run build",
"test": "ts-mocha --paths -p tsconfig.tests.json 'tests/**/*.spec.ts'",
"postbuild": "sed -i '6 a // @ts-ignore' dist/collections/ReadOnlyDictionary.d.ts"
"postbuild": "sed -i '7 a // @ts-ignore' dist/collections/ReadOnlyDictionary.d.ts"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand All @@ -31,6 +31,7 @@
"chai": "^4.2.0",
"cross-env": "^7.0.2",
"mocha": "^8.0.1",
"mocha-param": "^2.0.1",
"ts-mocha": "^7.0.0",
"ts-node": "^8.10.2",
"tsconfig-paths": "^3.9.0",
Expand Down
4 changes: 4 additions & 0 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export class Errors {
return new Error('An item with the same key has already been added');
}

public static elementAlreadyAdded(): Error {
return new Error('The element has already been added');
}

public static keyNotInDictionary(): Error {
return new Error('The given key was not present in the dictionary');
}
Expand Down
13 changes: 4 additions & 9 deletions src/collections/Dictionary.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Errors } from '../Errors';
import { Pair } from '../models/Pair';

import { ReadOnlyDictionary } from './ReadOnlyDictionary';

Expand All @@ -8,11 +7,7 @@ import { ReadOnlyDictionary } from './ReadOnlyDictionary';
*/
export class Dictionary<TKey, TValue> extends ReadOnlyDictionary<TKey, TValue> {
public add(key: TKey, value: TValue): this {
if (this.map.has(key)) {
throw Errors.itemWithKeyAlreadyAdded();
}

this.map.set(key, new Pair<TKey, TValue>(key, value));
this.addInternal(key, value);
return this;
}

Expand All @@ -21,15 +16,15 @@ export class Dictionary<TKey, TValue> extends ReadOnlyDictionary<TKey, TValue> {
}

public clear(): void {
this.map.clear();
this.innerDictionary.clear();
}

public remove(key: TKey): boolean {
return this.map.delete(key);
return this.innerDictionary.remove(key);
}

public set(key: TKey, value: TValue): this {
this.map.set(key, new Pair<TKey, TValue>(key, value));
this.innerDictionary.set(key, value);
return this;
}
}
21 changes: 12 additions & 9 deletions src/collections/HashSet.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { Errors } from '../Errors';

import { ReadOnlyHashSet } from './ReadOnlyHashSet';

/**
* Represents a set of values.
*/
export class HashSet<T> extends ReadOnlyHashSet<T> {
public constructor(source?: Iterable<T>) {
super(source ?? []);
}

public add(value: T): this {
this.source.add(value);
public add(element: T): this {
this.addInternal(element);
return this;
}

Expand All @@ -18,10 +16,15 @@ export class HashSet<T> extends ReadOnlyHashSet<T> {
}

public clear(): void {
this.source.clear();
this.internalHashSet.clear();
}

public remove(value: T): boolean {
return this.source.delete(value);
public remove(element: T): boolean {
return this.internalHashSet.remove(element);
}

public set(element: T): this {
this.internalHashSet.set(element);
return this;
}
}
77 changes: 55 additions & 22 deletions src/collections/ReadOnlyDictionary.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,89 @@
import { MapEnumerable } from '../enumerables/MapEnumerable';
import { EqualityComparer } from '../comparers/EqualityComparer';
import { Errors } from '../Errors';
import { Pair } from '../models/Pair';

import { Enumerable } from './Enumerable';
import { DictionaryAbstraction } from './internal/DictionaryAbstraction';
import { EqualityComparerDictionary } from './internal/EqualityComparerDictionary';
import { SimpleDictionary } from './internal/SimpleDictionary';

/**
* Represents a read-only collection of keys and values. Values can be accessed by keys.
*/
// @ts-ignore
export class ReadOnlyDictionary<TKey, TValue> extends MapEnumerable<TKey, TValue> {
public constructor(source?: Iterable<Pair<TKey, TValue>>) {
super(ReadOnlyDictionary.getSourceMap(source));
export class ReadOnlyDictionary<TKey, TValue> extends Enumerable<Pair<TKey, TValue>> {
protected readonly innerDictionary: DictionaryAbstraction<TKey, TValue>;

public constructor();
public constructor(source?: Iterable<Pair<TKey, TValue>>);
public constructor(comparer?: EqualityComparer<TKey>);
public constructor(source?: Iterable<Pair<TKey, TValue>>, comparer?: EqualityComparer<TKey>);
public constructor(a?: Iterable<Pair<TKey, TValue>> | EqualityComparer<TKey>, b?: EqualityComparer<TKey>) {
super();

let source: Iterable<Pair<TKey, TValue>> | undefined;
let comparer: EqualityComparer<TKey> | undefined;

if (a instanceof EqualityComparer) {
source = undefined;
comparer = a;
} else {
source = a;
comparer = b;
}

if (comparer == null) {
this.innerDictionary = new SimpleDictionary<TKey, TValue>();
} else {
this.innerDictionary = new EqualityComparerDictionary<TKey, TValue>(comparer);
}

if (source != null) {
for (const pair of source) {
this.addInternal(pair.key, pair.value);
}
}
}

public get size(): number {
return this.map.size;
return this.innerDictionary.getSize();
}

private static getSourceMap<TKey, TValue>(source?: Iterable<Pair<TKey, TValue>>): Map<TKey, Pair<TKey, TValue>> {
const sourceEnumerable = Enumerable.from(source ?? []);
const mapped = sourceEnumerable.select<[TKey, Pair<TKey, TValue>]>(pair => [pair.key, pair]);
return new Map<TKey, Pair<TKey, TValue>>(mapped);
public [Symbol.iterator](): Iterator<Pair<TKey, TValue>> {
return this.innerDictionary[Symbol.iterator]();
}

public containsKey(key: TKey): boolean {
return this.map.has(key);
return this.innerDictionary.containsKey(key);
}

public get(key: TKey): TValue {
if (!this.map.has(key)) {
const pair = this.innerDictionary.getPair(key);
if (pair == null) {
throw Errors.keyNotInDictionary();
}

// tslint:disable-next-line:no-non-null-assertion // TODO MV remove
return this.map.get(key)!.value;
return pair.value;
}

public getOrDefault(key: TKey): TValue | undefined {
if (!this.map.has(key)) {
return undefined;
}

// tslint:disable-next-line:no-non-null-assertion // TODO MV remove
return this.map.get(key)!.value;
const pair = this.innerDictionary.getPair(key);
return pair?.value;
}

public keys(): Enumerable<TKey> {
return Enumerable.from(this.map.keys());
return this.innerDictionary.keys();
}

public values(): Enumerable<TValue> {
return Enumerable.from(this.map.values())
.select(x => x.value);
return this.innerDictionary.values();
}

protected addInternal(key: TKey, value: TValue): void {
if (this.containsKey(key)) {
throw Errors.itemWithKeyAlreadyAdded();
}

this.innerDictionary.set(key, value);
}
}
59 changes: 53 additions & 6 deletions src/collections/ReadOnlyHashSet.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,65 @@
import { SetEnumerable } from '../enumerables/SetEnumerable';
import { EqualityComparer } from '../comparers/EqualityComparer';
import { Errors } from '../Errors';

import { Enumerable } from './Enumerable';
import { EqualityComparerHashSet } from './internal/EqualityComparerHashSet';
import { HashSetAbstraction } from './internal/HashSetAbstraction';
import { SimpleHashSet } from './internal/SimpleHashSet';

/**
* Represents a read-only set of values.
*/
export class ReadOnlyHashSet<T> extends SetEnumerable<T> {
public constructor(source?: Iterable<T>) {
super(new Set(source));
export class ReadOnlyHashSet<T> extends Enumerable<T> {
protected readonly internalHashSet: HashSetAbstraction<T>;

public constructor();
public constructor(source?: Iterable<T>);
public constructor(comparer?: EqualityComparer<T>);
public constructor(source?: Iterable<T>, comparer?: EqualityComparer<T>);
public constructor(a?: Iterable<T> | EqualityComparer<T>, b?: EqualityComparer<T>) {
super();

let source: Iterable<T> | undefined;
let comparer: EqualityComparer<T> | undefined;

if (a instanceof EqualityComparer) {
source = undefined;
comparer = a;
} else {
source = a;
comparer = b;
}

if (comparer == null) {
this.internalHashSet = new SimpleHashSet<T>();
} else {
this.internalHashSet = new EqualityComparerHashSet<T>(comparer);
}

if (source != null) {
for (const element of source) {
this.addInternal(element);
}
}
}

public [Symbol.iterator](): Iterator<T> {
return this.internalHashSet[Symbol.iterator]();
}

public get size(): number {
return this.source.size;
return this.internalHashSet.getSize();
}

public contains(element: T): boolean {
return this.source.has(element);
return this.internalHashSet.contains(element);
}

protected addInternal(element: T): void {
if (this.contains(element)) {
throw Errors.elementAlreadyAdded();
}

this.internalHashSet.set(element);
}
}
16 changes: 16 additions & 0 deletions src/collections/internal/DictionaryAbstraction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Pair } from '../../models/Pair';
import { Enumerable } from '../Enumerable';

export interface DictionaryAbstraction<TKey, TValue> {
[Symbol.iterator](): Iterator<Pair<TKey, TValue>>;

containsKey(key: TKey): boolean;
getPair(key: TKey): Pair<TKey, TValue> | undefined;
getSize(): number;
keys(): Enumerable<TKey>;
values(): Enumerable<TValue>;

clear(): void;
remove(key: TKey): boolean;
set(key: TKey, value: TValue): void;
}
Loading

0 comments on commit cf584f3

Please sign in to comment.