mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 01:31:02 +00:00
Refactoring
This commit is contained in:
parent
66e7b01282
commit
66bd3c78b9
4 changed files with 53 additions and 50 deletions
|
@ -2,14 +2,6 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// https://en.wikipedia.org/wiki/Bloom_filter
|
|
||||||
// https://khanlou.com/2018/09/bloom-filters/
|
|
||||||
// This implementation uses deterministic hashing functions so it can conform to Codable
|
|
||||||
|
|
||||||
enum BloomFilterError: Error {
|
|
||||||
case noHashesProvided
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct BloomFilter<T: DeterministicallyHashable>: Codable {
|
public struct BloomFilter<T: DeterministicallyHashable>: Codable {
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case hashes
|
case hashes
|
||||||
|
@ -20,12 +12,11 @@ public struct BloomFilter<T: DeterministicallyHashable>: Codable {
|
||||||
|
|
||||||
private var bits: BitArray
|
private var bits: BitArray
|
||||||
|
|
||||||
public init(hashes: Set<Hash>, byteCount: Int) throws {
|
public init(hashes: Set<Hash>, byteCount: Int) {
|
||||||
try self.init(hashes: hashes, data: Data(repeating: 0, count: byteCount))
|
self.init(hashes: hashes, data: Data(repeating: 0, count: byteCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(hashes: Set<Hash>, data: Data) throws {
|
public init(hashes: Set<Hash>, data: Data) {
|
||||||
guard !hashes.isEmpty else { throw BloomFilterError.noHashesProvided }
|
|
||||||
// Sort the hashes for consistent decoding output
|
// Sort the hashes for consistent decoding output
|
||||||
self.hashes = Array(hashes.sorted { $0.rawValue < $1.rawValue })
|
self.hashes = Array(hashes.sorted { $0.rawValue < $1.rawValue })
|
||||||
bits = BitArray(data: data)
|
bits = BitArray(data: data)
|
||||||
|
|
|
@ -12,18 +12,8 @@ final class CodableBloomFilterTests: XCTestCase {
|
||||||
XCTAssertEqual(Hash.fnv1a32.apply("hash"), 3469047761)
|
XCTAssertEqual(Hash.fnv1a32.apply("hash"), 3469047761)
|
||||||
}
|
}
|
||||||
|
|
||||||
func noHashesProvided() throws {
|
|
||||||
XCTAssertThrowsError(try BloomFilter<String>(hashes: [], byteCount: 8)) {
|
|
||||||
guard case BloomFilterError.noHashesProvided = $0 else {
|
|
||||||
XCTFail("Expected no hashers provided error")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testContains() throws {
|
func testContains() throws {
|
||||||
var sut = try BloomFilter<String>(hashes: [.sdbm32, .djb232], byteCount: 8)
|
var sut = BloomFilter<String>(hashes: [.sdbm32, .djb232], byteCount: 8)
|
||||||
|
|
||||||
sut.insert("lol")
|
sut.insert("lol")
|
||||||
sut.insert("ok")
|
sut.insert("ok")
|
||||||
|
@ -35,7 +25,7 @@ final class CodableBloomFilterTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testData() throws {
|
func testData() throws {
|
||||||
var sut = try BloomFilter<String>(hashes: [.sdbm32, .djb232], byteCount: 8)
|
var sut = BloomFilter<String>(hashes: [.sdbm32, .djb232], byteCount: 8)
|
||||||
|
|
||||||
sut.insert("lol")
|
sut.insert("lol")
|
||||||
sut.insert("ok")
|
sut.insert("ok")
|
||||||
|
@ -44,7 +34,7 @@ final class CodableBloomFilterTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFromData() throws {
|
func testFromData() throws {
|
||||||
let sut = try BloomFilter<String>(hashes: [.sdbm32, .djb232], data: Data([0, 16, 0, 0, 0, 2, 0, 144]))
|
let sut = BloomFilter<String>(hashes: [.sdbm32, .djb232], data: Data([0, 16, 0, 0, 0, 2, 0, 144]))
|
||||||
|
|
||||||
XCTAssert(sut.contains("lol"))
|
XCTAssert(sut.contains("lol"))
|
||||||
XCTAssert(sut.contains("ok"))
|
XCTAssert(sut.contains("ok"))
|
||||||
|
@ -53,7 +43,7 @@ final class CodableBloomFilterTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCoding() throws {
|
func testCoding() throws {
|
||||||
var sut = try BloomFilter<String>(hashes: [.sdbm32, .djb232], byteCount: 8)
|
var sut = BloomFilter<String>(hashes: [.sdbm32, .djb232], byteCount: 8)
|
||||||
let expectedData = Data(#"{"data":"ABAAAAACAJA=","hashes":["djb232","sdbm32"]}"#.utf8)
|
let expectedData = Data(#"{"data":"ABAAAAACAJA=","hashes":["djb232","sdbm32"]}"#.utf8)
|
||||||
|
|
||||||
sut.insert("lol")
|
sut.insert("lol")
|
||||||
|
@ -88,7 +78,7 @@ final class CodableBloomFilterTests: XCTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDataEncodingStrategy() throws {
|
func testDataEncodingStrategy() throws {
|
||||||
var sut = try BloomFilter<String>(hashes: [.sdbm32, .djb232], byteCount: 8)
|
var sut = BloomFilter<String>(hashes: [.sdbm32, .djb232], byteCount: 8)
|
||||||
let expectedData = Data(#"{"data":"0010000000020090","hashes":["djb232","sdbm32"]}"#.utf8)
|
let expectedData = Data(#"{"data":"0010000000020090","hashes":["djb232","sdbm32"]}"#.utf8)
|
||||||
|
|
||||||
sut.insert("lol")
|
sut.insert("lol")
|
||||||
|
|
|
@ -61,7 +61,7 @@ public extension InstanceURLService {
|
||||||
|
|
||||||
func updateFilter() -> AnyPublisher<Never, Error> {
|
func updateFilter() -> AnyPublisher<Never, Error> {
|
||||||
httpClient.request(UpdatedFilterTarget())
|
httpClient.request(UpdatedFilterTarget())
|
||||||
.handleEvents(receiveOutput: { userDefaultsClient.updatedInstanceFilter = $0 })
|
.handleEvents(receiveOutput: { userDefaultsClient.updateInstanceFilter($0) })
|
||||||
.ignoreOutput()
|
.ignoreOutput()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
@ -81,13 +81,43 @@ private struct UpdatedFilterTarget: DecodableTarget {
|
||||||
private extension InstanceURLService {
|
private extension InstanceURLService {
|
||||||
static let httpsPrefix = "https://"
|
static let httpsPrefix = "https://"
|
||||||
static let shortestPossibleURLLength = 4
|
static let shortestPossibleURLLength = 4
|
||||||
// swiftlint:disable line_length
|
static let defaultFilter = BloomFilter<String>(
|
||||||
static let defaultFilterData = #"{"hashes":["djb232","djb2a32","fnv132","fnv1a32","sdbm32"],"data":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAIAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAgAAAAAQAAAAAABAAACAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAABAAAEAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAIAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAIAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAIAAAQAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAQAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAADAAAAAAAAAAAAA=="}"#
|
hashes: [.djb232, .djb2a32, .sdbm32, .fnv132, .fnv1a32],
|
||||||
.data(using: .utf8)!
|
data: Data([
|
||||||
// swiftlint:enable line_length
|
0, 0, 0, 16, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
// swiftlint:disable force_try
|
0, 2, 2, 8, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 0, 0, 0,
|
||||||
static let defaultFilter = try! JSONDecoder().decode(BloomFilter<String>.self, from: defaultFilterData)
|
128, 0, 0, 32, 0, 128, 0, 0, 0, 4, 16, 4, 32, 0, 0, 16, 16, 4, 32, 0, 0, 128, 0, 16, 0, 0, 0, 0, 0, 0, 0, 4,
|
||||||
// swiftlint:enable force_try
|
0, 0, 0, 0, 4, 0, 0, 3, 2, 0, 0, 0, 4, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 132, 0, 0, 64, 0, 0, 0, 2,
|
||||||
|
0, 0, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
||||||
|
0, 0, 0, 96, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 8, 0, 1, 0, 8, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 160,
|
||||||
|
0, 0, 0, 8, 64, 0, 1, 32, 0, 0, 1, 0, 0, 0, 0, 64, 8, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 32, 0, 0, 0, 0, 0, 130, 65, 0, 4, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 8, 0, 0, 0, 0, 128, 65, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 64, 0, 128, 0, 0, 0, 16,
|
||||||
|
0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 2, 128, 0, 1, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 8, 0, 0, 0, 0, 0, 0, 0, 16, 0, 1, 48, 0, 0, 0, 2, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 16, 0, 0, 0, 16, 0, 16, 0, 2, 64,
|
||||||
|
0, 0, 0, 128, 0, 0, 0, 64, 16, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 8, 0, 0, 0, 4, 8, 0, 64, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 66,
|
||||||
|
0, 64, 0, 16, 8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 1, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 10, 0,
|
||||||
|
0, 4, 0, 0, 0, 1, 24, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 128, 4, 64, 0, 128, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 8, 0, 8, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 16, 0, 2, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 129, 8, 0, 8, 0, 0, 0, 8, 0, 0,
|
||||||
|
0, 2, 0, 0, 128, 8, 36, 32, 0, 64, 0, 0, 0, 4, 0, 32, 0, 0, 0, 0, 0, 16, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 8, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 16, 0, 0, 0, 136, 128, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 64, 0, 64, 0, 0, 0, 16, 0, 0, 0, 8, 0, 0, 0, 0, 16, 0,
|
||||||
|
0, 0, 0, 32, 0, 4, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0,
|
||||||
|
0, 0, 0, 8, 0, 4, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 0, 0, 160, 0, 0, 0, 4, 0, 0, 16, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 64,
|
||||||
|
16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 16, 0, 0, 0, 4, 0, 1, 0, 0, 0, 16, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 128, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 4, 0, 64, 0, 1, 4, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 12, 16, 0, 72, 0, 0, 0, 0, 0, 0
|
||||||
|
]))
|
||||||
|
|
||||||
var filter: BloomFilter<String> {
|
var filter: BloomFilter<String> {
|
||||||
userDefaultsClient.updatedInstanceFilter ?? Self.defaultFilter
|
userDefaultsClient.updatedInstanceFilter ?? Self.defaultFilter
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import CodableBloomFilter
|
import CodableBloomFilter
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
final class UserDefaultsClient {
|
struct UserDefaultsClient {
|
||||||
private let userDefaults: UserDefaults
|
private let userDefaults: UserDefaults
|
||||||
|
|
||||||
init(userDefaults: UserDefaults) {
|
init(userDefaults: UserDefaults) {
|
||||||
|
@ -13,23 +13,15 @@ final class UserDefaultsClient {
|
||||||
|
|
||||||
extension UserDefaultsClient {
|
extension UserDefaultsClient {
|
||||||
var updatedInstanceFilter: BloomFilter<String>? {
|
var updatedInstanceFilter: BloomFilter<String>? {
|
||||||
get {
|
guard let data = self[.updatedFilter] as Data? else {
|
||||||
guard let data = self[.updatedFilter] as Data? else {
|
return nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return try? JSONDecoder().decode(BloomFilter<String>.self, from: data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set {
|
return try? JSONDecoder().decode(BloomFilter<String>.self, from: data)
|
||||||
var data: Data?
|
}
|
||||||
|
|
||||||
if let newValue = newValue {
|
func updateInstanceFilter( _ filter: BloomFilter<String>) {
|
||||||
data = try? JSONEncoder().encode(newValue)
|
userDefaults.set(try? JSONEncoder().encode(filter), forKey: Item.updatedFilter.rawValue)
|
||||||
}
|
|
||||||
|
|
||||||
self[.updatedFilter] = data
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue