Skip to content

Commit

Permalink
Add intersect method: returns an intersection of two sequences.
Browse files Browse the repository at this point in the history
  • Loading branch information
Indomitable committed Aug 22, 2019
1 parent 6db9320 commit c27b4cd
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 42 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Methods implemented:
- `firstOrDefault`
- `groupJoin`
- `join`
- `intersect`
- `last`
- `lastOrDefault`
- `max`
Expand Down Expand Up @@ -96,7 +97,6 @@ Methods implemented:
Waiting for implementation:
- `contains`
- `except`
- `intersect`
- `zip`

Extra methods
Expand Down
10 changes: 9 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ declare module 'modern-linq' {

/**
* Selects all items of base type
* @param type
* @param typeCheck
*/
ofType<TOutput extends TValue>(typeCheck: (item: TValue) => item is TOutput): LinqIterable<TOutput>;

Expand Down Expand Up @@ -181,6 +181,14 @@ declare module 'modern-linq' {
*/
union(secondIterable: Iterable<TValue>): LinqIterable<TValue>;


/**
* Return an intersection of two iterables where the result is distinct values.
* @param secondIterable
* @param comparer optional predicate, if none is provided a default one (===) is used.
*/
intersect(secondIterable: Iterable<TValue>, comparer?: (first: TValue, second: TValue) => boolean): LinqIterable<TValue>;

/**
* Create a paging
* @param pageSize
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { TakeWhileIterable } from "./iterables/take-while";
import { SkipWhileIterable } from "./iterables/skip-while";
import { TakeLastIterable } from "./iterables/take-last";
import { SkipLastIterable } from "./iterables/skip-last";
import { IntersectIterable } from "./iterables/intersect";

// note: if using class as output we can just apply the mixin to BaseLinqIterable.
applyMixin(linqMixin, [
Expand Down Expand Up @@ -49,6 +50,7 @@ applyMixin(linqMixin, [
SkipWhileIterable,
TakeLastIterable,
SkipLastIterable,
IntersectIterable,
]);

export { fromIterable, fromObject, fromArrayLike, range, from, repeat } from './creation';
44 changes: 44 additions & 0 deletions src/iterables/intersect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { BaseLinqIterable } from "../base-linq-iterable";
import { defaultEqualityComparer, doneValue, iteratorResultCreator } from "../utils";
import { from } from "../creation";
import { WhereIterable } from "./where";

/**
* Returns an intersect of two sequences, does not return duplicates
*/
export class IntersectIterable extends BaseLinqIterable {
constructor(source, other, comparer) {
super(source);
this.other = other;
this.comparer = !!comparer ? comparer : defaultEqualityComparer;
}

__createContainingChecker() {
class Checker {
constructor(other, comparer) {
this.other = from(other).distinct(comparer).toArray();
this.comparer = comparer;
}

has(value) {
const index = from(this.other).firstIndex(item => this.comparer(value, item));
if (index > -1) {
this.other.splice(index, 1);
return true;
}
return false;
}
}
return new Checker(this.other, this.comparer);
}

[Symbol.iterator]() {
const checker = this.__createContainingChecker(this.other, this.comparer);
const iterator = this._getSourceIterator();
return {
next() {
return WhereIterable.__findNext(iterator, (item) => checker.has(item));
}
}
}
}
5 changes: 3 additions & 2 deletions src/iterables/where.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NativeProcessingLinqIterable } from "../base-linq-iterable";
import { doneValue, iteratorResultCreator } from "../utils";

/**
* Return filtred array [1, 2, 3, 4].where(x => x % 2 === 0) === [2, 4]
Expand All @@ -23,11 +24,11 @@ export class WhereIterable extends NativeProcessingLinqIterable {
while (!done) {
const next = iterator.next();
if (!next.done && predicate(next.value)) {
return { done: false, value: next.value };
return iteratorResultCreator(next.value);
}
done = next.done;
}
return { done: true };
return doneValue();
}

[Symbol.iterator]() {
Expand Down
4 changes: 4 additions & 0 deletions src/linq-mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { SkipWhileIterable } from "./iterables/skip-while";
import { TakeLastIterable } from "./iterables/take-last";
import { SkipLastIterable } from "./iterables/skip-last";
import { LastFinalizer } from "./finalizers/last";
import { IntersectIterable } from "./iterables/intersect";

export const linqMixin = {
where(predicate) {
Expand Down Expand Up @@ -98,6 +99,9 @@ export const linqMixin = {
union(secondIterable) {
return new UnionIterable(this, secondIterable);
},
intersect(secondIterable, comparer) {
return new IntersectIterable(this, secondIterable, comparer);
},
page(pageSize) {
return new PageIterable(this, pageSize);
},
Expand Down
102 changes: 64 additions & 38 deletions test/ts/tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
* This should be just basic tests which should verify if the typings are correct.
* For functionality testing, test should be placed in corresponding ../unit/*.spec.js file.
*/
import { expect } from "chai";
import { from, range, fromIterable, fromObject, fromArrayLike, repeat } from "modern-linq";
import { Person, Pet } from "../unit/models";
import {expect} from "chai";
import {from, fromArrayLike, fromIterable, fromObject, range, repeat} from "modern-linq";
import {Person, Pet} from "../unit/models";

describe("typescript tests", () => {
it("typescript test 0", () => {
const res = range(0, 10)
.where(_ => _ % 2 === 1)
.select(_ => _ * 2)
.groupBy(_ => _ > 6, _ => _, (key, items) => ({ key, items: items.toArray() }))
.groupBy(_ => _ > 6, _ => _, (key, items) => ({key, items: items.toArray()}))
.toArray();
expect(res).to.deep.equals([
{ key: false, items: [2, 6] },
{ key: true, items: [10, 14, 18] }
{key: false, items: [2, 6]},
{key: true, items: [10, 14, 18]}
]);
});

Expand All @@ -31,7 +31,7 @@ describe("typescript tests", () => {
new Person(30, "J", [new Pet("j0", "J")]),
];
const res = from(input)
.selectMany(_ => _.pets, (o, p) => ({ owner: o, pet: p}))
.selectMany(_ => _.pets, (o, p) => ({owner: o, pet: p}))
.groupBy(_ => _.owner.age)
.select(gr => ({
age: gr.key,
Expand All @@ -48,26 +48,26 @@ describe("typescript tests", () => {
});

it("fromObject", () => {
const res0 = Array.from(fromObject({ "a": 1, 2: "b" }));
expect(res0).to.deep.equals([{ key: "2", value: "b" }, { key: "a", value: 1}]);
const res0 = Array.from(fromObject({"a": 1, 2: "b"}));
expect(res0).to.deep.equals([{key: "2", value: "b"}, {key: "a", value: 1}]);

const res1 = Array.from(fromObject({ "a": 1, 2: "b" }, (k, v) => ([k, v])));
expect(res1).to.deep.equals([ ["2", "b"], ["a", 1] ]);
const res1 = Array.from(fromObject({"a": 1, 2: "b"}, (k, v) => ([k, v])));
expect(res1).to.deep.equals([["2", "b"], ["a", 1]]);
});

it("fromArrayLike", () => {
const res = Array.from(fromArrayLike({ 0: "a", 1: "b", 2: "c", length: 3 }));
const res = Array.from(fromArrayLike({0: "a", 1: "b", 2: "c", length: 3}));
expect(res).to.deep.equals(["a", "b", "c"]);
});

it("from", () => {
const resIterable = Array.from(from(new Set([1, 2, 3])));
expect(resIterable).to.deep.equal([1, 2, 3]);

const resObject = Array.from(from({ 1: "a", 2: "b" }));
expect(resObject).to.deep.equal([{ key: "1", value: "a"}, { key: "2", value: "b"}]);
const resObject = Array.from(from({1: "a", 2: "b"}));
expect(resObject).to.deep.equal([{key: "1", value: "a"}, {key: "2", value: "b"}]);

const resArrayLike = Array.from(from({ 0: "a", 1: "b", 2: "c", length: 3 }));
const resArrayLike = Array.from(from({0: "a", 1: "b", 2: "c", length: 3}));
expect(resArrayLike).to.deep.equals(["a", "b", "c"]);
});

Expand All @@ -93,9 +93,10 @@ describe("typescript tests", () => {
});

it("where type check", () => {
function isNum (val: string | number): val is number {
function isNum(val: string | number): val is number {
return typeof val === "number";
}

const res = Array.from(from([1, "a", 2, "b"]).where(isNum));
expect(res).to.deep.equals([1, 2]);
});
Expand All @@ -108,13 +109,16 @@ describe("typescript tests", () => {
it("select many", () => {
const source = {
0: ["a", "b"],
1: [ "c" ]
1: ["c"]
};
const res0 = Array.from(from(source).selectMany(_ => _.value));
expect(res0).to.deep.equal(["a", "b", "c"]);

const res1 = Array.from(from(source).selectMany(_ => _.value, (parent, child) => ({index: parent.key, child: child})));
expect(res1).to.deep.equal([ { index: "0", child: "a" }, { index: "0", child: "b" }, { index: "1", child: "c" }]);
const res1 = Array.from(from(source).selectMany(_ => _.value, (parent, child) => ({
index: parent.key,
child: child
})));
expect(res1).to.deep.equal([{index: "0", child: "a"}, {index: "0", child: "b"}, {index: "1", child: "c"}]);
});

it("take", () => {
Expand Down Expand Up @@ -157,7 +161,9 @@ describe("typescript tests", () => {

it("ofType", () => {
const source = [
1, "a", true, Symbol.for("b"), undefined, null, () => { return 1 }, {}, false
1, "a", true, Symbol.for("b"), undefined, null, () => {
return 1
}, {}, false
];
const resNum = Array.from(from(source).ofType("number"));
expect(resNum).to.deep.equal([1]);
Expand All @@ -166,15 +172,15 @@ describe("typescript tests", () => {
const resBool = Array.from(from(source).ofType("boolean"));
expect(resBool).to.deep.equal([true, false]);
const resSymbol = Array.from(from(source).ofType("symbol"));
expect(resSymbol).to.deep.equal([ Symbol.for("b") ]);
expect(resSymbol).to.deep.equal([Symbol.for("b")]);
const resUndefined = Array.from(from(source).ofType("undefined"));
expect(resUndefined).to.deep.equal([ undefined ]);
expect(resUndefined).to.deep.equal([undefined]);
const resFunc = Array.from(from(source).ofType("function"));
expect(resFunc).to.deep.equal([ source[6] ]);
expect(resFunc).to.deep.equal([source[6]]);
const resObject = Array.from(from(source).ofType("object"));
expect(resObject).to.deep.equal([ null, {} ]);
expect(resObject).to.deep.equal([null, {}]);

function isNum (val: string | number): val is number {
function isNum(val: string | number): val is number {
return typeof val === "number";
}

Expand All @@ -183,7 +189,9 @@ describe("typescript tests", () => {
});

it('ofClass', () => {
abstract class X {}
abstract class X {
}

class A extends X {
}

Expand Down Expand Up @@ -219,18 +227,24 @@ describe("typescript tests", () => {
expect(from([1, 2, 3]).firstOrDefault(5)).to.be.equal(1);
expect(from([]).firstOrDefault(5)).to.be.equal(5);

expect(from([1, 2, 3]).firstOrDefault(5,x => x === 2)).to.be.equal(2);
expect(from([]).firstOrDefault(5,x => x === 10)).to.be.equal(5);
expect(from([1, 2, 3]).firstOrDefault(10,x => x === 10)).to.be.equal(10);
expect(from([1, 2, 3]).firstOrDefault(5, x => x === 2)).to.be.equal(2);
expect(from([]).firstOrDefault(5, x => x === 10)).to.be.equal(5);
expect(from([1, 2, 3]).firstOrDefault(10, x => x === 10)).to.be.equal(10);
});

it('firstOrThrow', () => {
expect(from([1, 2, 3]).firstOrThrow()).to.be.equal(1);
expect(function () { from([]).firstOrThrow() }).to.throw(TypeError);
expect(function () {
from([]).firstOrThrow()
}).to.throw(TypeError);

expect(from([1, 2, 3]).firstOrThrow(x => x === 2)).to.be.equal(2);
expect(function () { from([]).firstOrThrow(x => x === 2) }).to.throw(TypeError);
expect(function () { from([1, 2, 3]).firstOrThrow(x => x === 10) }).to.throw(TypeError);
expect(function () {
from([]).firstOrThrow(x => x === 2)
}).to.throw(TypeError);
expect(function () {
from([1, 2, 3]).firstOrThrow(x => x === 10)
}).to.throw(TypeError);
});

it('last', () => {
Expand All @@ -246,23 +260,35 @@ describe("typescript tests", () => {
expect(from([1, 2, 3]).lastOrDefault(5)).to.be.equal(3);
expect(from([]).lastOrDefault(5)).to.be.equal(5);

expect(from([1, 2, 3]).lastOrDefault(5,x => x === 2)).to.be.equal(2);
expect(from([]).lastOrDefault(5,x => x === 10)).to.be.equal(5);
expect(from([1, 2, 3]).lastOrDefault(10,x => x === 10)).to.be.equal(10);
expect(from([1, 2, 3]).lastOrDefault(5, x => x === 2)).to.be.equal(2);
expect(from([]).lastOrDefault(5, x => x === 10)).to.be.equal(5);
expect(from([1, 2, 3]).lastOrDefault(10, x => x === 10)).to.be.equal(10);
});

it('lastOrThrow', () => {
expect(from([1, 2, 3]).lastOrThrow()).to.be.equal(3);
expect(function () { from([]).lastOrThrow() }).to.throw(TypeError);
expect(function () {
from([]).lastOrThrow()
}).to.throw(TypeError);

expect(from([1, 2, 3]).lastOrThrow(x => x === 2)).to.be.equal(2);
expect(function () { from([]).lastOrThrow(x => x === 2) }).to.throw(TypeError);
expect(function () { from([1, 2, 3]).lastOrThrow(x => x === 10) }).to.throw(TypeError);
expect(function () {
from([]).lastOrThrow(x => x === 2)
}).to.throw(TypeError);
expect(function () {
from([1, 2, 3]).lastOrThrow(x => x === 10)
}).to.throw(TypeError);
});

it('lastIndex', () => {
expect(from([1, 2, 3, 2, 3]).lastIndex(x => x === 3)).to.be.equal(4);
expect(from([]).lastIndex(x => x === 3)).to.be.equal(-1);
expect(from([1, 2, 3]).lastIndex(x => x === 10)).to.be.equal(-1);
});

it('intersect', () => {
expect(from(new Set([1, 2, 3])).intersect([3, 2]).toArray()).to.deep.equal([2, 3]);
expect(from([1, 2, 3, 2]).intersect([3, 2, 3]).toArray()).to.deep.equal([2, 3]);
expect(from([1, 2, 3, 2]).intersect([3, 2, 3], (a, b) => a === b).toArray()).to.deep.equal([2, 3]);
});
});
29 changes: 29 additions & 0 deletions test/unit/intersect.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { expect } from 'chai';
import { from } from "../../src";

describe('intersect tests', () => {
it('should do intersection of to sequences', () => {
expect(from([1, 2, 2, 3]).intersect([2, 1, 5, 5]).toArray()).to.deep.equal([1, 2]);

expect(from([
{ id: 1, x: 'a' },
{ id: 2, x: 'b' },
{ id: 3, x: 'c' },
]).intersect([
{ id: 4, x: 'a' },
{ id: 5, x: 'c' },
{ id: 2, x: 'b' },
{ id: 2, x: 'd' },
], (a, b) => a.id === b.id).toArray()).to.deep.equal([{ id: 2, x: 'b' }]);
});

it('should return noting if second has no elements', () => {
expect(from(new Set([1, 2, 3])).intersect([]).toArray()).to.deep.equal([]);
});

it('should return noting if no matches', () => {
expect(from(new Set([1, 2, 3])).intersect(new Set([4, 5, 6])).toArray()).to.deep.equal([]);
expect(from(new Set([1, 2, 3])).intersect(new Set([4, 5, 6]), (a, b) => a === b).toArray()).to.deep.equal([]);
});
});

0 comments on commit c27b4cd

Please sign in to comment.