-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #862
- Loading branch information
Showing
3 changed files
with
136 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import Map from 'es6-map'; | ||
import arrayFrom from 'array-from'; | ||
import { intersectionBy } from 'lodash'; | ||
|
||
export function zinterstore(destKey, numKeys, ...keys) { | ||
const srcMaps = []; | ||
|
||
if (parseInt(numKeys, 10) !== keys.length) { | ||
throw new Error('ERR syntax error'); | ||
} | ||
|
||
for (let i = 0; i < keys.length; i += 1) { | ||
// @TODO investigate a more stable way to detect sorted lists | ||
if (!this.data.has(keys[i]) || this.data.get(keys[i]) instanceof Map) { | ||
srcMaps.push(this.data.get(keys[i])); | ||
} | ||
} | ||
|
||
// deep copy inputs | ||
const inputs = srcMaps.map(x => | ||
JSON.parse(JSON.stringify(arrayFrom(x.values()))) | ||
); | ||
|
||
const intersected = intersectionBy(...inputs, 'value'); | ||
|
||
if (intersected.length === 0) { | ||
// make sure we don't have destKey set anymore | ||
this.data.delete(destKey); | ||
|
||
return 0; | ||
} | ||
|
||
// @TODO: support AGGREGATE option | ||
// @TODO: support WEIGHTS option | ||
// aggregate weights | ||
for (let i = 0; i < intersected.length; i += 1) { | ||
let weightSum = 0; | ||
|
||
for (let j = 0; j < srcMaps.length; j += 1) { | ||
if (srcMaps[j].get(intersected[i].value)) { | ||
weightSum += srcMaps[j].get(intersected[i].value).score; | ||
} | ||
} | ||
|
||
intersected[i].score = weightSum; | ||
intersected[i] = [intersected[i].value, intersected[i]]; | ||
} | ||
|
||
const intersectedMap = new Map(intersected); | ||
|
||
// store new sorted set | ||
this.data.set(destKey, intersectedMap); | ||
|
||
return intersected.length; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import Map from 'es6-map'; | ||
import expect from 'expect'; | ||
|
||
import MockRedis from '../../src'; | ||
|
||
describe('zinterstore', () => { | ||
const data = { | ||
foo: new Map([ | ||
['first', { score: 1, value: 'first' }], | ||
['second', { score: 2, value: 'second' }], | ||
['third', { score: 3, value: 'third' }], | ||
['fourth', { score: 4, value: 'fourth' }], | ||
['fifth', { score: 5, value: 'fifth' }], | ||
]), | ||
bar: new Map([ | ||
['first', { score: 1, value: 'first' }], | ||
['third', { score: 3, value: 'third' }], | ||
['fourth', { score: 4, value: 'fourth' }], | ||
['tenth', { score: 10, value: 'tenth' }], | ||
]), | ||
baz: new Map([ | ||
['twentieth', { score: 20, value: 'twentieth' }], | ||
['thirtieth', { score: 30, value: 'thirtiest' }], | ||
]), | ||
}; | ||
|
||
it('should intersect two sorted sets and return the number of resulting elements', () => { | ||
const redis = new MockRedis({ data }); | ||
|
||
return redis | ||
.zinterstore('dest', 2, 'foo', 'bar') | ||
.then(res => expect(res).toEqual(3)); | ||
}); | ||
|
||
it('should return 0 if the intersection is an empty set', () => { | ||
const redis = new MockRedis({ data }); | ||
|
||
return redis | ||
.zinterstore('dest', 2, 'foo', 'baz') | ||
.then(res => expect(res).toEqual(0)); | ||
}); | ||
|
||
it('should not create a sorted set if the intersection is an empty set', () => { | ||
const redis = new MockRedis({ data }); | ||
|
||
return redis.zinterstore('dest', 2, 'foo', 'baz').then(() => { | ||
expect(redis.data.get('dest')).toBe(undefined); | ||
}); | ||
}); | ||
|
||
it('should intersect two sorted sets with the correct data in the specified key', () => { | ||
const redis = new MockRedis({ data }); | ||
|
||
return redis.zinterstore('dest', 2, 'foo', 'bar').then(() => { | ||
expect(redis.data.get('dest')).toEqual( | ||
new Map([ | ||
['first', { score: 2, value: 'first' }], | ||
['third', { score: 6, value: 'third' }], | ||
['fourth', { score: 8, value: 'fourth' }], | ||
]) | ||
); | ||
}); | ||
}); | ||
|
||
it('should throw a syntax error if more keys specified than numKeys', () => { | ||
const redis = new MockRedis({ data }); | ||
|
||
return redis | ||
.zinterstore('dest', 2, 'foo', 'bar', 'baz') | ||
.catch(err => expect(err.message).toBe('ERR syntax error')); | ||
}); | ||
|
||
it('should throw a syntax error if fewer keys specified than numKeys', () => { | ||
const redis = new MockRedis({ data }); | ||
|
||
return redis | ||
.zinterstore('dest', 3, 'foo', 'bar') | ||
.catch(err => expect(err.message).toBe('ERR syntax error')); | ||
}); | ||
}); |