Skip to content

Commit

Permalink
feat: add toIterable function (#52)
Browse files Browse the repository at this point in the history
If the tree is very large, and you want to look at the contents it
would be good to not have to put every contact into an array before
you can look at any of them.

Adds a `toIterable` function as a generator that can be used to iterate
over the tree without buffering it's contents:

```js
for (const contact of tree.toIterable()) {
  // ... do something with contact
}
```
  • Loading branch information
achingbrain authored Feb 5, 2021
1 parent fff76ec commit 3d80061
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 0 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ As more contacts are added to the "far" k-bucket and it reaches its capacity, it
* [kBucket.metadata](#kbucketmetadata)
* [kBucket.remove(id)](#kbucketremoveid)
* [kBucket.toArray()](#kbuckettoarray)
* [kBucket.toIterable()](#kbuckettoiterable)
* [Event 'added'](#event-added)
* [Event 'ping'](#event-ping)
* [Event 'removed'](#event-removed)
Expand Down Expand Up @@ -205,6 +206,12 @@ Removes `contact` with the provided `id`.

Traverses the tree, putting all the contacts into one array.

#### kBucket.toIterable()

* Return: _Iterable_ All of the contacts in the tree, as an iterable

Traverses the tree, yielding contacts as they are encountered.

#### kBucket._determineNode(node, id [, bitIndex = 0])

_**CAUTION: reserved for internal use**_
Expand Down
16 changes: 16 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,22 @@ class KBucket extends EventEmitter {
return result
}

/**
* Similar to `toArray()` but instead of buffering everything up into an
* array before returning it, yield contacts as they are encountered while
* walking the tree
*/
* toIterable () {
for (const nodes = [ this.root ]; nodes.length > 0;) {
const node = nodes.pop()
if (node.contacts === null) {
nodes.push(node.right, node.left)
} else {
yield * node.contacts
}
}
}

/**
* Updates the contact selected by the arbiter.
* If the selection is our old contact and the candidate is some new contact
Expand Down
41 changes: 41 additions & 0 deletions test/toIterable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict'
var test = require('tape')
var KBucket = require('../')

function collect (iterable) {
var out = []

for (const thing of iterable) {
out.push(thing)
}

return out
}

test('toIterable should return empty iterable if no contacts', function (t) {
var kBucket = new KBucket()
t.same(collect(kBucket.toIterable()).length, 0)
t.end()
})

test('toIterable should return all contacts in an iterable arranged from low to high buckets', function (t) {
t.plan(22)
var kBucket = new KBucket({ localNodeId: Buffer.from([ 0x00, 0x00 ]) })
var expectedIds = []
for (var i = 0; i < kBucket.numberOfNodesPerKBucket; ++i) {
kBucket.add({ id: Buffer.from([ 0x80, i ]) }) // make sure all go into "far away" bucket
expectedIds.push(0x80 * 256 + i)
}
// cause a split to happen
kBucket.add({ id: Buffer.from([ 0x00, 0x80, i - 1 ]) })
// console.log(require('util').inspect(kBucket, {depth: null}))
var contacts = collect(kBucket.toArray())
// console.log(require('util').inspect(contacts, {depth: null}))
t.same(contacts.length, kBucket.numberOfNodesPerKBucket + 1)
t.same(parseInt(contacts[0].id.toString('hex'), 16), 0x80 * 256 + i - 1)
contacts.shift() // get rid of low bucket contact
for (i = 0; i < kBucket.numberOfNodesPerKBucket; ++i) {
t.same(parseInt(contacts[i].id.toString('hex'), 16), expectedIds[i])
}
t.end()
})

0 comments on commit 3d80061

Please sign in to comment.