每个项目具有多个键的树数据结构

Tree data structure with multiple keys for each item

我正在使用 node js 开发一个显示在线用户列表的聊天应用程序。每个用户都有以下属性:

ipv4,rank,score,登录时间

访问用户时使用ipv4

应按照此排序策略列出用户:

例如:

1。登录 userA (ipv4:1.1.1.1, rank:0, score:3000, loginTime:1631966424070)

2。登录 userB (ipv4:2.2.2.2, rank:1, score:2000, loginTime:1631966424080)

3。登录 userC (ipv4:3.3.3.3, rank:1, score:1000, loginTime:1631966424090)

4。登录 userD (ipv4:4.4.4.4, rank:0, score:3000, loginTime:1631966424100)

访问示例:

获取用户(ipv4:3.3.3.3) -> return userC


有很多用户,我使用functional-red-black-tree数据结构来提高性能。 在这个数据结构中,每个项目都有一个键和一个值。 这棵树的构造函数接受一个比较器函数——类似数组排序比较器——带有两个参数,每个参数都是键,作为参数。

我定义了 user 如下:

class Key {
    constructor(ipv4, rank, score, loginTime) {
        this.ipv4 = ipv4;
        this.rank = rank;
        this.score = score;
        this.loginTime = loginTime;
    }
}

class OnlineUser {

    constructor(ipv4, rank, score, loginTime, name, avatar, bio, gender) {
        this.key = new Key(ipv4, rank, score, loginTime);
        this.name = name;
        this.avatar = avatar;
        this.bio = bio;
        this.gender = gender;
    }
}
module.exports = { OnlineUser, Key };

我用这样的比较函数定义了

const createTree = require('functional-red-black-tree');

var tree = createTree((userAKey, userBKey) => {
    if (userAKey.ipv4 == userBKey.ipv4) return 0;
    if (userAKey.rank < userBKey.rank) return 1;
    if (userAKey.rank > userBKey.rank) return -1;
    if (userAKey.score < userBKey.score) return 1;
    if (userAKey.score > userBKey.score) return -1;
    if (userAKey.loginTime < userBKey.loginTime) return -1;
    if (userAKey.loginTime > userBKey.loginTime) return 1;
});

并添加用户:

tree = tree.insert(user.key, user);

问题: 所有用户都以正确的顺序添加。但是要访问或删除或...用户通常 returned undefined:

const OnlineUser = require('./model/OnlineUser').OnlineUser;
const Key = require('./model/OnlineUser').Key;

for (let i = 0; i < 1000; i++) {
    let user = new OnlineUser(i, rand(), rand(), rand(), '', '', '', 1);
    tree = tree.insert(user.key, user);
}
console.log(tree.get(new Key(10, 0, 0, 0)));

function rand() {
    return Math.floor(Math.random() * 100);
}

我认为这个问题是因为比较器函数使用不同的值进行比较和相等。

在我看来的解决方案是对所有密钥进行哈希处理,并将其变成一个哈希如下的密钥:

function hash(ipv4, rank, score, loginTime){
    let hashCode;
    //How to implement?
    return hashCode;
}

在您的 rand 示例中,键 (10, 0, 0, 0) 不匹配是正常的,即使 ipv4=10 在树中的某个位置。这是因为该节点将更像 (10, 66, 35, 19)... 不会遇到,因为树的 ordering 主要基于等级,而不是在 ipv4 上。决定在树中向下钻取的位置将基于等级 (0),移动到树的左侧,而您要查找的节点可能在右侧(更高等级 66)。

你可以做的是用 Map 伴随树,它将 ipv4 值映射到现有的 Key 实例。所以每次在树中插入的时候,也在那个Map里面添加对应的映射。每次您想查找或删除时,首先通过您拥有的 ipv4 值从该映射中检索 Key 实例,然后将该 Key 实例传递给树的 getremove方法。

这是一个包装器 class,可以实现这种组合。使用您想要包含的任何其他方法对其进行扩展:

const createTree = require('functional-red-black-tree');

class Key {
    constructor(ipv4, rank, score, loginTime) {
        this.ipv4 = ipv4;
        this.rank = rank;
        this.score = score;
        this.loginTime = loginTime;
    }
}

class OnlineUser {
    constructor(ipv4, rank, score, loginTime, name, avatar, bio, gender) {
        this.key = new Key(ipv4, rank, score, loginTime);
        this.name = name;
        this.avatar = avatar;
        this.bio = bio;
        this.gender = gender;
    }
}

class Tree {
    constructor(comparator) {
        this.tree = createTree(comparator);
        this.map = new Map;
    }
    insert(user) {
        this.tree = this.tree.insert(user.key, user);
        this.map.set(user.key.ipv4, user.key);
    }
    get(ipv4) {
        let key = this.map.get(ipv4);
        if (key) return this.tree.get(key);
    }
    remove(ipv4) {
        let key = this.map.get(ipv4);
        if (key) return tree.remove(key);
    }
}

let tree = new Tree((userAKey, userBKey) =>
    userBKey.rank - userAKey.rank ||
    userBKey.score - userAKey.score ||
    userBKey.loginTime - userAKey.loginTime
);

// Insert  some data:
let userA = new OnlineUser("1.1.1.1", 0, 3000, 1631966424070, "userA");
let userB = new OnlineUser("2.2.2.2", 1, 2000, 1631966424080, "userB");
let userC = new OnlineUser("3.3.3.3", 1, 1000, 1631966424090, "userC");
let userD = new OnlineUser("4.4.4.4", 0, 3000, 1631966424100, "userD");

for (let user of [userA, userB, userC, userD]) {
    tree.insert(user);
}

// Make use of the redblack `forEach`:
tree.tree.forEach((key, value) => console.log(value.name))
// Use our own `get` method:
console.log(tree.get("3.3.3.3")); // userC