Skip to content

Commit

Permalink
implement cartesian product following issue #11
Browse files Browse the repository at this point in the history
  • Loading branch information
xgbuils committed May 20, 2017
1 parent 0757968 commit f388eec
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 79 deletions.
54 changes: 38 additions & 16 deletions src/gen/cartesian.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,46 @@
function* cartesian (...iterables) {
const stack = []
const length = iterables.length - 1
const arr = Array(length)
const {length} = iterables
const iterators = iterables
.map(iterable => iterable[Symbol.iterator]())
const states = iterators.map(it => it.next())
if (states.some(({done}) => done)) {
return
}
const cache = states.map(({value}) => [value])
const steps = Array(length).fill(0)
yield cacheToItem(cache, steps)
let index = 0
const generators = iterables
.map(iterable => iterable[Symbol.iterator].bind(iterable))
let iterator = generators[index]()
while (index >= 0) {
const status = iterator.next()
if (status.done) {
--index
iterator = stack.pop()
} else if (index < length) {
arr[index] = status.value
index = stack.push(iterator)
iterator = generators[index]()
let pos = 0
let iterator = iterators[index]

while (index < length) {
++steps[pos]
let newItem
if (pos < index) {
newItem = steps[pos] < cache[pos].length
} else {
yield arr.concat([status.value])
const {done, value} = iterator.next()
newItem = !done
if (!done) {
cache[pos].push(value)
} else {
++index
}
}

if (newItem) {
steps.fill(0, 0, pos)
yield this(cacheToItem(cache, steps))
pos = 0
} else {
++pos
iterator = iterators[pos]
}
}
}

function cacheToItem (cache, steps) {
return cache.map((c, i) => c[steps[i]])
}

module.exports = cartesian
167 changes: 104 additions & 63 deletions test/fn/cartesian_test.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,128 @@
const {expect} = require('chai')
const Iterum = require('../../src/index.js')
const {range} = Iterum

describe('Iterum.cartesian', function () {
describe('given 1 list', function () {
it('cartesian product of one iterable with one element', function () {
const iterable = Iterum([1]).cartesian()
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([[1]])
})

it('cartesian product of one iterable with one element', function () {
const iterable = Iterum([1, 2, 3]).cartesian()
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([[1], [2], [3]])
})
})
describe('given 2 lists, it makes cartesian product of these lists', function () {
it('2 no empty lists', function () {
const cartesianIterable = Iterum([1, 2]).cartesian([3, 4])
expect([...cartesianIterable]).to.be.deep.equal([
[1, 3],
[1, 4],
[2, 3],
[2, 4]
])
const iterable = Iterum([1, 2]).cartesian([3, 4])
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([
[1, 3],
[2, 3],
[1, 4],
[2, 4]
])
})

it('first list is empty', function () {
const cartesianIterable = Iterum([]).cartesian([1, 2, 3, 4])
expect([...cartesianIterable]).to.be.deep.equal([])
const iterable = Iterum([]).cartesian([1, 2, 3, 4])
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([])
})

it('second list is empty', function () {
const cartesianIterable = Iterum([1, 2, 3, 4]).cartesian([])
expect([...cartesianIterable]).to.be.deep.equal([])
const iterable = Iterum([1, 2, 3, 4]).cartesian([])
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([])
})

it('2 lists are empty', function () {
const cartesianIterable = Iterum([]).cartesian([])
expect([...cartesianIterable]).to.be.deep.equal([])
const iterable = Iterum([]).cartesian([])
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([])
})

it('first list has one element', function () {
const cartesianIterable = Iterum([0]).cartesian([1, 2, 3, 4])
expect([...cartesianIterable]).to.be.deep.equal([
[0, 1],
[0, 2],
[0, 3],
[0, 4]
])
const iterable = Iterum([0]).cartesian([1, 2, 3, 4])
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([
[0, 1],
[0, 2],
[0, 3],
[0, 4]
])
})
})

describe('0 parameters', function () {
it('no empty list', function () {
const cartesianIterable = Iterum([1, 2, 3, 4]).cartesian()
expect([...cartesianIterable]).to.be.deep.equal([
[1], [2], [3], [4]
])
const iterable = Iterum([1, 2, 3, 4]).cartesian()
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([[1], [2], [3], [4]])
})

it('empty list', function () {
const cartesianIterable = Iterum([]).cartesian()
expect([...cartesianIterable]).to.be.deep.equal([])
const iterable = Iterum([]).cartesian()
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([])
})
})

describe('more than 2 lists', function () {
it('3 no empty lists with the same length', function () {
const cartesianIterable = Iterum([1, 2]).cartesian([3, 4], [5, 6])
expect([...cartesianIterable]).to.be.deep.equal([
[1, 3, 5],
[1, 3, 6],
[1, 4, 5],
[1, 4, 6],
[2, 3, 5],
[2, 3, 6],
[2, 4, 5],
[2, 4, 6]
])
const iterable = Iterum([1, 2]).cartesian([3, 4], [5, 6])
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([
[1, 3, 5],
[2, 3, 5],
[1, 4, 5],
[2, 4, 5],
[1, 3, 6],
[2, 3, 6],
[1, 4, 6],
[2, 4, 6]
])
})

it('3 no empty lists with different length', function () {
const cartesianIterable = Iterum([1, 2]).cartesian([3], [4, 5, 6])
expect([...cartesianIterable]).to.be.deep.equal([
[1, 3, 4],
[1, 3, 5],
[1, 3, 6],
[2, 3, 4],
[2, 3, 5],
[2, 3, 6]
])
const iterable = Iterum([1, 2]).cartesian([3], [4, 5, 6])
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([
[1, 3, 4],
[2, 3, 4],
[1, 3, 5],
[2, 3, 5],
[1, 3, 6],
[2, 3, 6]
])
})

it('there is an empty list', function () {
const cartesianIterable = Iterum([1, 2, 3]).cartesian(new Set([3, 4, 5, 3, 2, 4]), [], [4, 5, 6])
expect([...cartesianIterable]).to.be.deep.equal([])
const iterable = Iterum([1, 2, 3]).cartesian(new Set([3, 4, 5, 3, 2, 4]), [], [4, 5, 6])
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([])
})
})

it('product of potentially infinite iterables', function () {
const naturals = range(0, Infinity)
const iterable = Iterum(naturals)
.cartesian(naturals)
.take(5)
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([
[0, 0],
[1, 0],
[2, 0],
[3, 0],
[4, 0]
])
})

describe('bad arguments', function () {
it('throws an exception when it is passed no iterable in 1st argument', function () {
function foo () {
Expand All @@ -106,9 +143,10 @@ describe('Iterum.cartesian', function () {

describe('converting iterum instance to array', function () {
it('returns the same as converting [Symbol.iterator]() iterator to array', function () {
const cartesianIterable = Iterum([1, 3]).cartesian([6, 10])
const iterator = cartesianIterable[Symbol.iterator]()
expect([...iterator]).to.be.deep.equal([...cartesianIterable])
const iterable = Iterum([1, 3]).cartesian([6, 10])
const iterator = iterable[Symbol.iterator]()
expect([...iterator].map(e => [...e]))
.to.be.deep.equal([...iterable].map(e => [...e]))
})
})

Expand All @@ -118,26 +156,29 @@ describe('Iterum.cartesian', function () {
const a = Iterum(iterable).cartesian()
const b = Iterum(a)
expect(a).to.be.not.equal(b)
expect([...a]).to.be.deep.equal([...b])
expect([...a].map(e => [...e]))
.to.be.deep.equal([...b].map(e => [...e]))
})
})

describe('static method', function () {
it('normal behaviour', function () {
const cartesianIterable = Iterum.cartesian(new Set([3, 8, 5]), 'ac')
expect([...cartesianIterable]).to.be.deep.equal([
[3, 'a'],
[3, 'c'],
[8, 'a'],
[8, 'c'],
[5, 'a'],
[5, 'c']
])
const iterable = Iterum.cartesian(new Set([3, 8, 5]), 'ac')
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([
[3, 'a'],
[8, 'a'],
[5, 'a'],
[3, 'c'],
[8, 'c'],
[5, 'c']
])
})

it('replaces first parameter by empty iterable when is not an iterable', function () {
const cartesianIterable = Iterum.cartesian({}, 'ac')
expect([...cartesianIterable]).to.be.deep.equal([])
const iterable = Iterum.cartesian({}, 'ac')
expect([...iterable].map(e => [...e]))
.to.be.deep.equal([])
})
})
})

0 comments on commit f388eec

Please sign in to comment.