如何在 Swift 中为 Int 数组(自定义字符串结构)实现 Hashable 协议

2023-12-11

我正在制作一个结构,其作用就像String,不同之处在于它仅处理 Unicode UTF-32 标量值。因此,它是一个数组UInt32. (See 这个问题了解更多背景。)

我想做的事

我希望能够使用我的自定义ScalarStringstruct 作为字典中的键。例如:

var suffixDictionary = [ScalarString: ScalarString]() // Unicode key, rendered glyph value

// populate dictionary
suffixDictionary[keyScalarString] = valueScalarString
// ...

// check if dictionary contains Unicode scalar string key
if let renderedSuffix = suffixDictionary[unicodeScalarString] {
    // do something with value
}

Problem

为了做到这一点,ScalarString需要实施可哈希协议。我想我可以做这样的事情:

struct ScalarString: Hashable {

    private var scalarArray: [UInt32] = []

    var hashValue : Int {
        get {
            return self.scalarArray.hashValue // error
        }
    }
}

func ==(left: ScalarString, right: ScalarString) -> Bool {
    return left.hashValue == right.hashValue
}

但后来我发现斯威夫特数组没有hashValue.

我读到了什么

文章在 Swift 中实现 Hashable 协议的策略有很多很棒的想法,但我没有看到任何在这种情况下可以很好地发挥作用的想法。具体来说,

  • 对象属性(数组没有hashValue)
  • ID属性(不知道如何才能很好地实现)
  • Formula(似乎 32 位整数字符串的任何公式都会占用处理器资源,并且会出现大量整数溢出)
  • 对象标识符(我使用的是结构,而不是类)
  • 继承自 NSObject(我使用的是结构,而不是类)

以下是我读到的其他一些内容:

  • 实现 Swift 的 Hashable 协议
  • Swift 比较协议
  • 完美的哈希函数
  • Swift 数组和字典中自定义对象的成员资格
  • 如何为您的自定义类实现 Hashable
  • 在 Swift 中编写良好的 Hashable 实现

Question

Swift Strings 有一个hashValue财产,所以我知道这是可能做到的。

我将如何创建一个hashValue对于我的自定义结构?

Updates

更新1:我想做一些不涉及转换的事情String然后使用String's hashValue。我制作自己的结构的全部目的是为了避免做很多事情String转换。String得到它的hashValue从某个地方。看来我可以用同样的方法得到它。

更新2:我一直在研究其他上下文中字符串哈希码算法的实现。不过,我在知道哪个最好并用 Swift 表达它们时遇到了一些困难。

  • Java hashCode算法
  • C 算法
  • 字符串的哈希函数(所以C中的问题和答案)
  • 哈希教程(弗吉尼亚理工大学算法可视化研究小组)
  • 通用哈希函数算法

Update 3

我不想导入任何外部框架,除非这是推荐的方法。

我使用 DJB 哈希函数提交了一个可能的解决方案。


Update

马丁·Rwrites:

As of 斯威夫特 4.1,编译器可以综合Equatable and Hashable如果所有成员都符合,则自动实现类型一致性 Equatable/Hashable (SE0185)。并且截至斯威夫特 4.2,高质量的哈希 组合器内置于 Swift 标准库(SE-0206)中。

因此不再需要定义自己的散列 函数,只需声明一致性即可:

struct ScalarString: Hashable, ... {

    private var scalarArray: [UInt32] = []

    // ... }

因此,下面的答案需要重写(再次)。在此之前,请参阅上面链接中 Martin R 的回答。


旧答案:

提交我的答案后,这个答案已被完全重写代码审查的原始答案.

如何实现Hashable协议

The 可哈希协议允许您使用自定义类或结构作为字典键。为了实现这个协议,你需要

  1. 实施等值协议(Hashable继承自Equatable)
  2. 返回一个计算出来的hashValue

这些要点是根据文档中给出的公理得出的:

x == y暗示x.hashValue == y.hashValue

where x and y是某种类型的值。

实施 Equatable 协议

为了实现 Equatable 协议,您需要定义您的类型如何使用==(等价)运算符。在您的示例中,可以这样确定等效性:

func ==(left: ScalarString, right: ScalarString) -> Bool {
    return left.scalarArray == right.scalarArray
}

The ==函数是全局的,因此它超出了您的类或结构。

返回一个计算出来的hashValue

您的自定义类或结构还必须有一个计算的hashValue多变的。一个好的哈希算法将提供广泛的哈希值。但需要注意的是,您不需要保证哈希值都是唯一的。当两个不同的值具有相同的哈希值时,这称为哈希冲突。当发生碰撞时,它需要一些额外的工作(这就是为什么需要良好的分布),但有些碰撞是可以预料的。据我了解,==函数做了额外的工作。 (Update: 看起来像== may do all工作。)

有多种计算哈希值的方法。例如,您可以执行一些简单的操作,例如返回数组中的元素数量。

var hashValue: Int {
    return self.scalarArray.count
} 

每当两个数组具有相同数量的元素但不同的值时,就会产生哈希冲突。NSArray显然使用了这种方法。

DJB 哈希函数

与字符串一起使用的常见哈希函数是 DJB 哈希函数。这是我将要使用的,但请查看其他一些here.

快速实现由@MartinR提供如下:

var hashValue: Int {
    return self.scalarArray.reduce(5381) {
        ($0 << 5) &+ $0 &+ Int($1)
    }
}

这是我原始实现的改进版本,但让我也包括旧的扩展形式,这对于不熟悉的人来说可能更具可读性reduce。我相信这是等价的:

var hashValue: Int {

    // DJB Hash Function
    var hash = 5381

    for(var i = 0; i < self.scalarArray.count; i++)
    {
        hash = ((hash << 5) &+ hash) &+ Int(self.scalarArray[i])
    }

    return hash
} 

The &+运营商允许Int对于长字符串溢出并重新开始。

大局观

我们已经查看了这些片段,现在让我展示与 Hashable 协议相关的整个示例代码。ScalarString是问题中的自定义类型。当然,这对于不同的人来说会有所不同。

// Include the Hashable keyword after the class/struct name
struct ScalarString: Hashable {

    private var scalarArray: [UInt32] = []

    // required var for the Hashable protocol
    var hashValue: Int {
        // DJB hash function
        return self.scalarArray.reduce(5381) {
            ($0 << 5) &+ $0 &+ Int($1)
        }
    }
}

// required function for the Equatable protocol, which Hashable inheirits from
func ==(left: ScalarString, right: ScalarString) -> Bool {
    return left.scalarArray == right.scalarArray
}

其他有帮助的阅读

  • 哪种哈希算法最适合唯一性和速度?
  • 溢出运算符
  • 为什么 5381 和 33 在 djb2 算法中如此重要?
  • 如何处理哈希冲突?

Credits

非常感谢 Code Review 中的 Martin R。我的重写很大程度上是基于他的回答。如果您觉得这有帮助,请给他点赞。

Update

Swift 现在是开源的,所以可以看看如何hashValue实施用于String来自源代码。它似乎比我在这里给出的答案更复杂,我没有花时间对其进行全面分析。您可以自己这样做。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Swift 中为 Int 数组(自定义字符串结构)实现 Hashable 协议 的相关文章

随机推荐

  • Kafka流处理器线程安全吗?

    我知道这个问题之前在这里被问过 卡夫卡流并发 但这对我来说很奇怪 根据文档 或者也许我遗漏了一些东西 每个分区都有一个任务 意味着不同的处理器实例 并且每个任务都由不同的线程执行 但是当我测试它时 我发现不同的线程可以获得不同的处理器实例
  • 使用 PhantomJS 包含 js 文件

    在 PhantomJS 脚本中 我尝试加载定义数组的本地 JavaScript 文件 var webPage require webpage page webPage create injected page injectJs codes
  • 将 SupportMapFragment 放置在 DialogFragment 上

    我试图在 DialogFragment 上添加 SupportMapFragment 但它返回error inflating class fragment 我不明白为什么它会被退回error inflating class fragment
  • Zend 框架有文件结构的修复版本吗?

    作为 Zend 框架的新手 我对该框架有一些与版本相关的问题 Zend Framework 是否有固定的文件结构 即文件布局的固定形式 如果是这样 这个文件结构是否会根据框架版本而变化 如果是这样 是否有任何参考资料可以了解文件结构的所有差
  • R 中的对数概率图?

    Does anyone know how to create log probability plot like this one in R where the x axis is probability and y axis is in
  • ListFragment 作为 DialogFragment

    是否可以显示ListFragment as Dialog 或者没有办法 我应该实现我自己的ListView empty TextView和不确定的ProgressBar inside DialogFragment myself 另外一个选择
  • 关于“self”关键字

    void Foo void Foo 在该方法中 void Foo 关键字self表示该类的一个实例 但在方法中 void Foo 关键字是什么self意思是 这是否意味着Class self是每个方法的两个隐式参数之一 它是一个指向对象的指
  • 使用 SQLAlchemy 的 sql.func 注册自定义函数

    如何在 sqlalchemy 中应用自定义过滤器 我尝试过 hybrid property 和 hybrid method 然而 他们给出了错误 这是我的代码 class Product db Model tablename product
  • 每天在 Swift 中重置 NSUserDefault 键

    我正在编写一个应用程序 需要每天重置存储在 NSUserDefaults 中的密钥 00 00 时 我已经实现了这一目标 但我使用的方法是一种混乱且不可靠的方法 有没有简单的方法可以实现我的目标 这是代码 extension NSDate
  • WooCommerce 在结帐时使用 Optgroup 选择下拉菜单

    我在用着WordPress 5 0 2 with WooCommerce 3 5 3我在结帐页面上创建了一个选择下拉菜单 效果很好 但是我想在其中添加一些选项组来组织选择选项 这是我的代码函数 php add action woocomme
  • WatchKit 扩展包 ID 不可用

    我已将手表套件应用程序添加到我的 iOS 应用程序中 一切工作正常且运行良好 直到我想在两个应用程序之间共享数据 每当我尝试在手表套件扩展上添加 应用程序组 功能时 它都会告诉我我的捆绑包 ID com myrealappid watchk
  • 虚拟子域:每个用户一个子域

    在我的网站上 我使用虚拟主机 因此我的用户可以拥有虚拟域 如 user1 mydomain com user2 mydomain com 等 问题是 在 user1 domain com 等虚拟域上 索引页面始终与我的索引页面 http m
  • 检测未分配的局部变量的错误(当动态变量影响代码流预测时)

    文档意味着 out 参数在发送到函数之前不需要初始化 只需声明 然而 这段代码 class Program static void Main dynamic p string s if p null T out s System Conso
  • RStudio:使用 roxygen2 构建包。不生成 NAMESPACE 文件

    这是我第一次构建 R 包 并在 devtools 和 roxygen2 的帮助下完成 在 R 目录中编写一个简单的函数并使用 devtools 制作一个说明文件后 我第一次尝试构建并重新加载 但出现错误 gt devtools docume
  • 确定函数返回类型的最简单方法

    给定一个非常简单但冗长的函数 例如 int foo int a int b int c int d return 1 using ReturnTypeOfFoo 确定函数返回类型的最简单和简洁的方法是什么 ReturnTypeOfFoo 在
  • 正在加载 Apple Pay 送货地址 无街道

    我正在尝试从以下地址中提取送货地址ABRecordRef由苹果公司提供 我有以下内容 但我的街道总是返回nil ABMultiValueRef addresses ABRecordCopyValue abRecordRef kABPerso
  • 如何使用 P3D 渲染器实现 noSmooth()?

    我想使用 P3D 渲染器通过 PGraphics 实例渲染基本的 3D 形状 而无需任何锯齿 平滑 但 noSmooth 似乎不起作用 在 OF 我记得打电话给setTextureMinMagFilter GL NEAREST GL NEA
  • 在没有公共块的情况下通过子例程将一组变量值传递给函数有哪些方法?

    我不想在我的程序中使用公共块 我的主程序调用一个子例程 该子例程调用一个函数 该函数需要来自子例程的变量 将信息集从子例程传递到函数有哪些方法 program call CONDAT i j end program SUBROUTINE C
  • 未安装新组件的文件,因为旧组件具有相同的文件

    我们遇到重大更新时未安装文件的问题 我们有一个重大更新
  • 如何在 Swift 中为 Int 数组(自定义字符串结构)实现 Hashable 协议

    我正在制作一个结构 其作用就像String 不同之处在于它仅处理 Unicode UTF 32 标量值 因此 它是一个数组UInt32 See 这个问题了解更多背景 我想做的事 我希望能够使用我的自定义ScalarStringstruct