【问题标题】:Heterogeneous collection异构集合
【发布时间】:2021-11-03 19:54:01
【问题描述】:

在新版本的 C++ 中,您可以检查一个项目是否在 unordered_set(一个 HashSet)中,即使该项目与 unordered_set 的类型不同,同时保持 O(1) 时间复杂度.

我正在尝试找出如何在 Swift 中执行此操作。

这是 C++ 示例:

struct First {
    int data;
    std::string otherData;

    First(int data, std::string otherData) : data(data), otherData(otherData) { }
};
struct Second {
    int data;
    int otherData;

    Second(int data, int otherData) : data(data), otherData(otherData) { }
};

假设我想创建Firstunordered_set,但我想检查Second 对象是否在Set 中,通过其data 字段进行比较。你可以这样做:

struct Equal {
    using is_transparent = void;

    template<class F, class S>
    bool operator()(const F& lhs, const S& rhs) const {
        return lhs.data == rhs.data;
    }
};

struct Hash {
    using is_transparent = void;

    template<class T>
    size_t operator()(const T& t) const {
        return std::hash<int>{}(t.data);
    }
};

int main()
{
    std::unordered_set<First, Hash, Equal> set;
    set.insert(First(100, "test"));

    std::cout << set.contains(First(100, "bla")) << "\n"; // true
    std::cout << set.contains(Second(100, 1000)) << "\n"; // true
}

这很好用。但是,我不确定您将如何在 Swift 中实现这一点。在 Swift 中,Setunordered_set 相同,但其 contains 方法只接受该特定元素(无重载)。

您可以遍历所有元素,但会失去 O(1) HashSet 时间复杂度。

我想知道,这在 Swift 中是否可行?

【问题讨论】:

  • 您可以推出自己的函数,首先尝试将对象转换为ArrayLiteralElement,如果成功则使用contains
  • 不幸的是,您不能在 Swift 中将 FirstSecond 放在同一个 Set 中。您将: 1) 必须使FirstSecond 具有相同的数据类型,其中otherData 的类型为Any(或枚举大小写,例如.int(...).string(...))。 2) 只将data 放入集合中。如果可能,2 是更好的选择。如果不可能,我会使用一个枚举,每个案例都存储所需的值,这些值在FirstSecond 之间有所不同。如果你愿意,我可以写一个答案(你说你喜欢哪种方法)。
  • 我相信它是 Swift 源代码中的here,他们(私下)使用元素的hashValue 访问集合的一部分。能够直接检查一个元素是否在来自hashValue 的集合中将非常有用,因为这样这个问题就可以解决,而无需将您的数据结构更改为不太合乎逻辑的东西。
  • @George 在哈希表中,您不能仅使用哈希码直接获取值(可能存在冲突)。这就是为什么您还需要为元素实现Equatable,但不幸的是Equatable 仅适用于相同类型(我认为)
  • @George 我也在考虑可能对整个班级使用enum,所以枚举有 2 个值,First 和 Second,它仍然保持其值原理图

标签: c++ swift hashset


【解决方案1】:

为了满足基本要求(部分匹配),您可以使用contains(where:) 和谓词来比较元素的哈希值和目标的哈希。

class First:Hashable {
    var data:Int;
    var otherData:String;

    static func == (lhs:First, rhs:First) -> Bool {
        return lhs.data == rhs.data;
    }
    
    init(data:Int, otherData:String) {
        self.data = data;
        self.otherData = otherData;
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(data)
    }
};

class Second:Hashable {
    var data:Int;
    var otherData:Int;

    static func == (lhs:Second, rhs:Second) -> Bool {
        return lhs.data == rhs.data;
    }

    init(data:Int, otherData:Int) {
        self.data = data;
        self.otherData = otherData;
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(data)
    }
};

var set: Set = [First(data: 100, otherData: "test")];
print(set.contains(First(data: 100, otherData: "bla")));

var hasher = Hasher();
Second(data: 100, otherData: 1000).hash(into:&hasher);
var target = hasher.finalize();

print(set.contains(where: {(candidate:First) -> Bool in
                       var hasher = Hasher();
                       candidate.hash(into:&hasher);
                       return hasher.finalize() == target;
                   }));

为了满足性能要求,有(至少)两种选择:将可散列数据重构为公共基类,或编写扩展方法,使用可散列数据创建适当类型的临时元素。

将可散列数据移动到基类是最直接的,尽管结果 Set 只会在基类中是同质的。此外,如果您无法控制元素类的来源,则无法实现此方法。

定义类后,Set.contains(_:) 将按需要工作。

class Zeroth:Hashable {
    var data:Int;

    static func == (lhs:Zeroth, rhs:Zeroth) -> Bool {
        return lhs.data == rhs.data;
    }
    
    init(_ data:Int) {
        self.data = data;
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(data)
    }
};

class First:Zeroth {
    var otherData:String;

    init(data:Int, otherData:String) {
        self.otherData = otherData;
        super.init(data)
    }
};

class Second:Zeroth {
    var otherData:Int;

    init(data:Int, otherData:Int) {
        self.otherData = otherData;
        super.init(data)
    }
};

var test = First(data: 100, otherData: "test");
var bla = First(data: 100, otherData: "bla");

var set: Set<Zeroth> = [test];
print(set.contains(bla));


var member = Second(data: 100, otherData: 1000);

print(set.contains(member));

扩展方法最接近 C++ 接口。使用协议,以便可以将扩展方法限制为仅散列其部分数据的类。下面使用的协议还添加了一个方法partialCopy(from:),用于处理类之间的转换。

protocol DataElement {
    var data:Int {get}
    init(_ data:Int)
    static func partialCopy<Other:DataElement>(from other:Other) -> Self;
}

extension DataElement {
    static func partialCopy<Other:DataElement>(from other:Other) -> Self {
        return Self(other.data);
    }
}

class First:Hashable, DataElement {
    var data:Int;
    var otherData:String = "";

    static func == (lhs:First, rhs:First) -> Bool {
        return lhs.data == rhs.data;
    }

    required init(_ data:Int) {
        self.data = data;
    }
    
    init(data:Int, otherData:String) {
        self.data = data;
        self.otherData = otherData;
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(data)
    }
};

class Second:Hashable, DataElement {
    var data:Int;
    var otherData:Int = 0;

    static func == (lhs:Second, rhs:Second) -> Bool {
        return lhs.data == rhs.data;
    }

    required init(_ data:Int) {
        self.data = data;
    }
    
    init(data:Int, otherData:Int) {
        self.data = data;
        self.otherData = otherData;
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(data)
    }
};

var test = First(data: 100, otherData: "test");
var bla = First(data: 100, otherData: "bla");

var set: Set<First> = [test];
print(set.contains(bla));


extension Set where Element:DataElement {
    func contains<Other:DataElement>(matching member:Other) -> Bool {
        let matching : Element = Element.partialCopy(from:member); //Element(member.data);
        return self.contains(matching);
    }
}

var other = Second(data: 100, otherData: 1000);
print(set.contains(matching:other));

【讨论】:

  • 但是你失去了 Set 的 O(1) 时间复杂度
  • @whatisgoingon:答案已更新以匹配更新后的问题。
【解决方案2】:

方法#1

您可以使用枚举将FirstSecond 存储在同一个集合中。您将有一个First 的案例和一个Second 的案例。

在枚举的Hashable 一致性中,您应该散列两个结构之间相同的数据。 Equatable 一致性只是确保如果散列相等,它们是等价的,即使枚举大小写不同。

例子:

enum Both: Hashable {
    case first(First)
    case second(Second)

    func hash(into hasher: inout Hasher) {
        switch self {
        case .first(let first):
            hasher.combine(first.data)
        case .second(let second):
            hasher.combine(second.data)
        }
    }

    static func == (lhs: Both, rhs: Both) -> Bool {
        lhs.hashValue == rhs.hashValue
    }
}
struct First {
    let data: Int
    let otherData: String
}

struct Second {
    let data: Int
    let otherData: Int
}
let set: Set<Both> = [.first(First(data: 100, otherData: "test"))]

let first = First(data: 100, otherData: "bla")
print(set.contains(.first(first))) // true

let second = Second(data: 100, otherData: 1000)
print(set.contains(.second(second))) // true

方法#2

如果FirstSecond 必须是一个结构,这可能是不可能的。但是,如果他们不这样做,您可以拥有一个符合 Hashable 的超类。

例子:

class Superclass: Hashable {
    let data: Int

    init(data: Int) {
        self.data = data
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(data)
    }

    static func == (lhs: Superclass, rhs: Superclass) -> Bool {
        lhs.data == rhs.data
    }
}
class First: Superclass {
    let otherData: String

    init(data: Int, otherData: String) {
        self.otherData = otherData
        super.init(data: data)
    }
}

class Second: Superclass {
    let otherData: Int

    init(data: Int, otherData: Int) {
        self.otherData = otherData
        super.init(data: data)
    }
}
let set: Set<Superclass> = [First(data: 100, otherData: "test")]

let first = First(data: 100, otherData: "bla")
print(set.contains(first)) // true

let second = Second(data: 100, otherData: 1000)
print(set.contains(second)) // true

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-15
    相关资源
    最近更新 更多