四种编程语言中散列表的遍历顺序比较

简介: Ruby, JavaScript, Lua和Go是小博无线技术团队主要使用的四种编程语言。本文对这四种编程语言中散列表的遍历顺序进行了比较并总结了它们各自的特点。

测试方法

我们使用Ruby, JavaScript, Lua和Go这四种编程语言分别实现了以下步骤:

  1. 创建一个空的散列表H
  2. 以26个英文字母为key并按a-z的顺序插入H
  3. 遍历H并打印遍历的顺序,重复两次
  4. 从H删除"r", "p", "t", "k"这四个key
  5. 遍历H并打印遍历的顺序
  6. 将"r", "p", "t", "k"这四个key重新插入H
  7. 遍历H并打印遍历的顺序

测试环境

  • Ruby 2.3 for Ruby
  • Node.js 6.9.1 for JavaScript
  • Lua 5.2.2 for Lua
  • Go 1.7.1 for Go

Ruby

测试代码

def keys(t)
    r = ""
    t.each { |k, v| r += k }
    r
end

s = "abcdefghijklmnopqrstuvwxyz"
t = {}
s.each_char { |c| t[c] = 1 }
puts(keys(t))
puts(keys(t))

d = "rptk"
d.each_char { |c| t.delete(c) }
puts(keys(t))

d.each_char { |c| t[c] = 100 }
puts(keys(t))

输出及其分析

反复运行多次,输出无变化,内容如下:

abcdefghijklmnopqrstuvwxyz
abcdefghijklmnopqrstuvwxyz
abcdefghijlmnoqsuvwxyz
abcdefghijlmnoqsuvwxyzrptk

可见,Ruby的散列表是一个有序结构,其遍历顺序和插入顺序完全一致。

JavaScript

测试代码

var print = console.log

function keys(t) {
    r = ""
    for (k in t) {
        r += k
    }
    return r 
}

s = "abcdefghijklmnopqrstuvwxyz"
t = {}
for (i in s) {
    t[s[i]] = i
}
print(keys(t))
print(keys(t))

d = "rptk"
for (i in d) {
    delete t[d[i]]
}
print(keys(t))

for (i in d) {
    t[d[i]] = 100
}
print(keys(t))

输出及其分析

反复运行多次,输出无变化,和Ruby版本的输出完全一样(在此略去)。

这说明JavaScript的散列表也是一个有序结构,其遍历顺序与插入顺序相同。

Lua

测试代码

local function keys(t)
    r = ""
    for k in pairs(t) do
        r = r .. k
    end
    return r
end

s = "abcdefghijklmnopqrstuvwxyz"
t = {}
for i = 1, #s do
    t[s:sub(i, i)] = i
end
print(keys(t))
print(keys(t))

d = "rptk"
for i = 1, #d do
    t[d:sub(i, i)] = nil
end
print(keys(t))

for i = 1, #d do
    t[d:sub(i, i)] = 100
end
print(keys(t))

输出及其分析

第一次运行输出:

yzwxuvstabijghefcdqropmnkl
yzwxuvstabijghefcdqropmnkl
yzwxuvsabijghefcdqomnl
yzwxuvstabijghefcdqropmnkl

第二次运行输出:

srqpwvutzyxcbagfedkjihonml
srqpwvutzyxcbagfedkjihonml
sqwvuzyxcbagfedjihonml
srqpwvutzyxcbagfedkjihonml

不难看出,每次运行的输出都不一样,但在单次运行中,对散列表的遍历顺序是确定的,且与插入顺序无关。

Go

测试代码

package main

import "fmt"

func keys(t map[byte]int) string {
    r := ""
    for k := range t {
        r += string(k)
    }
    return r
}

func main() {
    s := "abcdefghijklmnopqrstuvwxyz"
    t := make(map[byte]int)
    for i := range s {
        t[s[i]] = i
    }
    fmt.Println(keys(t))
    fmt.Println(keys(t))

    d := "rptk"
    for i := range d {
        delete(t, d[i])
    }
    fmt.Println(keys(t))

    for i := range d {
        t[d[i]] = 100
    }
    fmt.Println(keys(t))
}

输出及其分析

第一次运行输出:

prbgioquvxeknjlmswcdfzyaht
bgipruvxeknoqmswcdfjlzahty
gibnoquvxedfjlmswczhya
noquvxekfjlmswcdztyahirpbg

第二次运行输出:

motbcghlrvzdeijsuyakpqfnwx
deijlrvzakpqsuyfnwxbcghmot
qsuyawxfnghmobcijlvzde
tbcghmovzdeijlryapkqsufnwx

不仅每次运行的输出都不一样,甚至每一行的输出都不一样。这说明Go的散列表遍历操作是完全随机的。

总结

  • Ruby和JavaScript竟然将散列表这种经典的无序数据结构选择了一种有序的实现,令人深感意外。两者的遍历顺序都和插入顺序一致。
  • Lua的散列表的遍历顺序和插入顺序无关,但表的遍历顺序在表内元素无变化时是确定的。
  • Go的散列表遍历顺序则是完全随机的,非常符合散列表是一种无序数据结构的特点。
  • 但不论编程语言采用了哪种具体的散列表实现办法,除非情况及其特殊,我们在编写代码时都应将散列表作为一种无序数据结构来使用,即在内心机器中,总是将散列表的遍历顺序当成是完全随机的。

问题

  1. Ruby是如何将散列表这种经典的无序数据结构实现为一种有序结构的?
  2. 上面Lua的测试代码每次运行的输出都不一样,这是为什么?
  3. Go的散列表实现极具个性,并完全体现了散列表作为“无序数据结构”的特点。它是如何做到这一点的?
目录
相关文章
|
6月前
|
算法
【算法与数据结构】二叉树(前中后)序遍历1
【算法与数据结构】二叉树(前中后)序遍历
|
3月前
【数据结构】二叉树顺序实现(大堆)-->赋源码
【数据结构】二叉树顺序实现(大堆)-->赋源码
37 4
|
5月前
|
算法 搜索推荐 C++
C++之STL常用算法(遍历、查找、排序、拷贝、替换、算数生成、集合)
C++之STL常用算法(遍历、查找、排序、拷贝、替换、算数生成、集合)
|
5月前
|
存储 算法
数据结构学习记录——集合及运算(集合的表示、并查集、树结构表示集合、集合运算、查找函数、并运算)
数据结构学习记录——集合及运算(集合的表示、并查集、树结构表示集合、集合运算、查找函数、并运算)
31 0
|
算法
【算法】递归解决各种数据结构的遍历问题
【算法】递归解决各种数据结构的遍历问题
55 0
|
6月前
|
算法 Java Go
【数据结构和算法】判断子序列
给定字符串s和t,判断s是否为t的子序列。 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 进阶: 如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?
91 3
|
算法 搜索推荐 C语言
c语言数据结构-排序(快速+归并+计数+桶)
c语言数据结构-排序(快速+归并+计数+桶)
c语言数据结构-排序(快速+归并+计数+桶)
|
算法 搜索推荐 C语言
c语言数据结构-排序(冒泡+选择+插入+希尔)
c语言数据结构-排序(冒泡+选择+插入+希尔)
|
C++
【数据结构】两种顺序表有序插入的函数
【数据结构】两种顺序表有序插入的函数
150 0
|
算法 程序员
Qz学算法-数据结构篇(表达式、递归)
数据结构的前缀、中缀、后缀表达式->(逆波兰表达式)和递归
142 0