{"id":13710021,"url":"https://github.com/kristoff-it/zig-cuckoofilter","last_synced_at":"2025-10-28T11:32:02.121Z","repository":{"id":49360707,"uuid":"186288939","full_name":"kristoff-it/zig-cuckoofilter","owner":"kristoff-it","description":"Production-ready Cuckoo Filters for any C ABI compatible target.","archived":false,"fork":false,"pushed_at":"2023-01-10T04:32:44.000Z","size":131,"stargazers_count":84,"open_issues_count":0,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-01T15:04:19.410Z","etag":null,"topics":["bloom-filter","c-abi","cffi","cuckoo-filter","filter","p11c","probabilistic-data-structures","zig"],"latest_commit_sha":null,"homepage":"","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kristoff-it.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-05-12T18:17:24.000Z","updated_at":"2024-12-27T23:30:11.000Z","dependencies_parsed_at":"2023-02-08T17:16:01.516Z","dependency_job_id":null,"html_url":"https://github.com/kristoff-it/zig-cuckoofilter","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristoff-it%2Fzig-cuckoofilter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristoff-it%2Fzig-cuckoofilter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristoff-it%2Fzig-cuckoofilter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kristoff-it%2Fzig-cuckoofilter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kristoff-it","download_url":"https://codeload.github.com/kristoff-it/zig-cuckoofilter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238646088,"owners_count":19506951,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bloom-filter","c-abi","cffi","cuckoo-filter","filter","p11c","probabilistic-data-structures","zig"],"created_at":"2024-08-02T23:00:50.714Z","updated_at":"2025-10-28T11:32:01.825Z","avatar_url":"https://github.com/kristoff-it.png","language":"Zig","funding_links":[],"categories":["Algorithms and data structures","Libraries","Language Essentials"],"sub_categories":["Data Structure and Algorithm"],"readme":"\u003ch1 align=\"center\"\u003ezig-cuckoofilter\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/kristoff-it/zig-cuckoofilter/releases/latest\"\u003e\u003cimg src=\"https://badgen.net/github/release/kristoff-it/zig-cuckoofilter\"/\u003e\u003c/a\u003e\n    \u003ca href=\"LICENSE.md\"\u003e\u003cimg src=\"https://badgen.net/github/license/kristoff-it/zig-cuckoofilter\" /\u003e\u003c/a\u003e\n    \u003ca href=\"https://twitter.com/croloris\"\u003e\u003cimg src=\"https://badgen.net/badge/twitter/@croloris/1DA1F2?icon\u0026label\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n\tProduction-ready Cuckoo Filters for any C ABI compatible target.\n\u003c/p\u003e\n\nUsed by\n-------\n\n[![kristoff-it/redis-cuckoofilter - GitHub](https://gh-card.dev/repos/kristoff-it/redis-cuckoofilter.svg)](https://github.com/kristoff-it/redis-cuckoofilter)\n\n\nWhat's a Cuckoo Filter?\n-----------------------\n\nCuckoo filters are a probabilistic data structure that allows you to test for \nmembership of an element in a set without having to hold the whole set in \nmemory.\n\nThis is done at the cost of having a probability of getting a false positive \nresponse, which, in other words, means that they can only answer \"Definitely no\" \nor \"Probably yes\". The false positive probability is roughly inversely related \nto how much memory you are willing to allocate to the filter.\n\nThe most iconic data structure used for this kind of task are Bloom filters \nbut Cuckoo filters boast both better practical performance and efficiency, and, \nmore importantly, the ability of **deleting elements from the filter**. \n\nBloom filters only support insertion of new items.\nSome extensions of Bloom filters have the ability of deleting items but they \nachieve so at the expense of precision or memory usage, resulting in a far worse \ntradeoff compared to what Cuckoo filters offer.\n\n\nWhat Makes This Library Interesting\n-----------------------------------\n\n### It's production-ready\nMost Cuckoo Filter implementations available on GitHub get the computer science \naspect right but fail at the engineering level, rendering each basically unsuitable\nfor most serious use cases. These problems go from not offering a way to \npersit and restore the filter, up to silent corruption of the filter when its \nfill-rate increases too much (because the implementation has no vacant/homeless slot). \nThis implementation covers all these aspects in full, giving to you complete control \nover the filter while making sure that misusage gets **always** properly reported.\n\n### It's for advanced users\nInstead of making a Bloom-like interface, I leave to the caller to\nchoose a hashing function to use, and which byte(s) of the original item to use\nas fingerprint. This would not produce good ergonomics with Bloom filters as\nthey rely on multiple hashings (dozens for low error rates!). \nThis requires the user to know the basics of hashing and how these data structures \nwork but on the upside:\n\t\n- If you are already handling hashed values, no superfluous work is done.\n- To perform well, Cuckoo filters rely on a good choice of fingerprint for each \n  item, and it should not be left to the library.\n- **The hash function can be decided by you, meaning that this library is \n  hashing-function agnostic**.\n\n### It's written in Zig (https://ziglang.org)\nWhich means that the code is clear, free of C gotchas, and that you can dynamically \nlink to it from any C ABI compatible language. Zig is simpler and safer than C but \noperates at the same abstraction level (i.e. no gc, explicit memory management, \npointers, etc). Zig can compile C code and Zig shared object files can be used \nin a normal C compilation process.\n\n### It doesn't manage memory\nAll memory allocation/freeing is done by the caller. This makes this library suitable\nfor embedding in any kind of ecosystem. For example, take a look at \n\t[kristoff-it/redis-cuckoofilter](https://github.com/kristoff-it/redis-cuckoofilter).\n\t\n\t\nUsing zig-cuckoofilter from C or other languages (Python, Go, JavaScript, ...)\n------------------------------------------------------------------------------\n\n### Read the provided examples\nIn [`c-abi-examples/`](c-abi-examples/) you will find a few different simple examples\non how to use the library from C code or in your language of choice by using the C \nForeign Function Interface.\n\n\n### Read the rest of this README \nTo learn how to use all the functionalities of this library, read the rest of the README.\nIt's Zig code so you can't copy it verbatim, but for each function there's a C equivalent in\n    [`cuckoofilter_c.zig`](src/cuckoofilter_c.zig).\n\n### Download binaries or compile it yourself\nYou can download pre-compiled binaries from the Release section on GitHub.\nTo compile the code yourself you need to \n    [install the Zig compiler](https://ziglang.org).\nEach file in [`c-abi-examples/`](c-abi-examples/) will have comments that will tell you more precisely what you need.\n\nPlease read the \n    [official Zig documentation](https://ziglang.org/documentation/master/)\nto learn more about available targets, build modes, static/dynamic linking, etc.\n\n\nUsage\n-----\n\n### Quickstart\n```zig\nconst std = @import(\"std\");\nconst hasher = std.hash.Fnv1a_64;\nconst cuckoo = @import(\"./src/cuckoofilter.zig\");\n\nfn fingerprint(x: []const u8) u8 {\n    return x[0];\n}\n\npub fn main() !void {\n    const universe_size = 1000000;\n    const memsize = comptime cuckoo.Filter8.size_for(universe_size);\n\n    var memory: [memsize]u8 align(cuckoo.Filter8.Align) = undefined;\n    var cf = try cuckoo.Filter8.init(memory[0..]);\n\n    const banana_h = hasher.hash(\"banana\");\n    const banana_fp = fingerprint(\"banana\");\n\n    const apple_h = hasher.hash(\"apple\");\n    const apple_fp = fingerprint(\"apple\");\n\n    _ = try cf.maybe_contains(banana_h, banana_fp); // =\u003e false\n    _ = try cf.count();                             // =\u003e 0\n    try cf.add(banana_h, banana_fp);\n    _ = try cf.maybe_contains(banana_h, banana_fp); // =\u003e true\n    _ = try cf.maybe_contains(apple_h, apple_fp);   // =\u003e false\n    _ = try cf.count();                             // =\u003e 1\n    try cf.remove(banana_h, banana_fp);\n    _ = try cf.maybe_contains(banana_h, banana_fp); // =\u003e false\n    _ = try cf.count();                             // =\u003e 0\n}\n```\n\n### Extended example\nThis is also available in [example.zig](example.zig).\n```zig\nconst std = @import(\"std\");\nconst hasher = std.hash.Fnv1a_64;\nconst cuckoo = @import(\"./src/cuckoofilter.zig\");\n\nfn fingerprint8(x: []const u8) u8 {\n    return x[0];\n}\n\nfn fingerprint32(x: []const u8) u32 {\n    // Just a sample strategy, not suitable for all types \n    // of input. Imagine if you were adding images to the\n    // filter: all fingerprints would be the same because\n    // most formats have a standard header. In that case\n    // you want to make sure to use the actual graphical\n    // data to pluck your fingerprint from.\n    return @bytesToSlice(u32, x[0..@sizeOf(u32)])[0];\n}\n\npub fn main() !void {\n\n    // Assume we want to keep track of max 1 Million items.\n    const universe_size = 1000000;\n\n    // Let's use Filter8, a filter with 1 byte long  \n    // fingerprints and a 3% max *false positive* error rate.\n    // Note: Cuckoo filters cannot produce false negatives.\n    \n    // Error % information:\n    _ = cuckoo.Filter8.MaxError;\n     // ╚═\u003e 3.125e-02 (~0.03, i.e. 3%)\n    _ = cuckoo.Filter16.MaxError;\n     // ╚═\u003e 1.22070312e-04 (~0.0001, i.e. 0.01%)\n    _ = cuckoo.Filter32.MaxError;\n     // ╚═\u003e 9.31322574e-10 (~0.000000001, i.e. 0.0000001%)\n \n\n    // First let's calculate how big the filter has to be:\n    const memsize = comptime cuckoo.Filter8.size_for(universe_size);\n\n    // The value of memsize has to be a power of two and it \n    // is *strongly* recommended to keep the fill rate of a \n    // filter under 80%. size_for() will pad the number for \n    // you automatically and then round up to the closest \n    // power of 2. size_for_exactly() will not apply any \n    // padding before rounding up.\n\n    // Use capacity() to know how many items a slice of memory  \n    // can store for the given filter type.\n    _ = cuckoo.Filter8.capacity(memsize); // =\u003e 2097152 \n    // Note: this function will return the theoretical maximum\n    // capacity, without subtracting any padding. It's smart\n    // to adjust your expectations to match how much memory\n    // you have to allocate anyway, but don't get too greedy.\n    // I say `theoretical` because an overfilled filter will\n    // start refusing inserts with a TooFull error.\n\n    // This is how you allocate static memory for the filter:\n    var memory: [memsize]u8 align(cuckoo.Filter8.Align) = undefined;\n\n    // Note: the filter benefits from a specific alignment \n    // (which differs from type to type) so you must specify it\n    // when allocating memory. Failing to do so will result in\n    // a comptime error.\n\n    // Instantiating a filter\n    var cf8 = try cuckoo.Filter8.init(memory[0..]);\n\n    //\n    // FILTER USAGE\n    //\n    const banana_h = hasher.hash(\"banana\");\n    const banana_fp = fingerprint8(\"banana\");\n\n    const apple_h = hasher.hash(\"apple\");\n    const apple_fp = fingerprint8(\"apple\");\n\n    _ = try cf8.maybe_contains(banana_h, banana_fp); // =\u003e false\n    _ = try cf8.count();                             // =\u003e 0\n    try cf8.add(banana_h, banana_fp);\n    _ = try cf8.maybe_contains(banana_h, banana_fp); // =\u003e true\n    _ = try cf8.maybe_contains(apple_h, apple_fp);   // =\u003e false\n    _ = try cf8.count();                             // =\u003e 1\n    try cf8.remove(banana_h, banana_fp);\n    _ = try cf8.maybe_contains(banana_h, banana_fp); // =\u003e false\n    _ = try cf8.count();                             // =\u003e 0\n\n    // The filter can also be used with dynamic memory.\n    // It's up to you to manage that via an allocator.\n    const example_allocator = std.heap.c_allocator;\n\n    // Don't forget to free the memory afterwards.\n    const memsize32 = comptime cuckoo.Filter32.size_for_exactly(64);\n    var dyn_memory = try example_allocator.alignedAlloc(u8, cuckoo.Filter32.Align, memsize32);\n    defer example_allocator.free(dyn_memory);\n\n    var dyn_cf32 = try example_allocator.create(cuckoo.Filter32);\n    defer example_allocator.destroy(dyn_cf32);\n    dyn_cf32.* = try cuckoo.Filter32.init(dyn_memory);\n\n\n    // When restoring a persisted filter, you should only persist the individual fields\n    // as, for example, .buckets is a slice that points to `dyn_memory` which would be \n    // invalid upon restore (wrong pointer) and just a waste of space when stored.\n    // Upon loading, to reconnect the filter and its `dyn_memory`, use bytesToBuckets.\n    // Here's an example (which is not necessary to make this script work, as we just created\n    // the entire filter):\n    //\n    // dyn_cf32.buckets = cuckoo.Filter32.bytesToBuckets(dyn_memory);\n\n    // \n    // USAGE FAILURE SCENARIOS\n    //\n\n    // 1. Adding too many colliding items (because of bad entropy or\n    //    because you are adding multiple copies of the same item)\n    const pear_h = hasher.hash(\"pear\");\n    const pear_fp = fingerprint32(\"pear\");\n    try dyn_cf32.add(pear_h, pear_fp);\n    try dyn_cf32.add(pear_h, pear_fp);\n    try dyn_cf32.add(pear_h, pear_fp);\n    try dyn_cf32.add(pear_h, pear_fp);\n    try dyn_cf32.add(pear_h, pear_fp);\n\n    // No more space for items with equal hash and fp,\n    // next insert will fail.\n    dyn_cf32.add(pear_h, pear_fp) catch |err| switch (err) {\n        error.TooFull =\u003e std.debug.warn(\"yep, too full\\n\"),\n        else =\u003e unreachable,\n    };\n\n    // Other inserts that don't collide can still succeed\n    const orange_h = hasher.hash(\"orange\");\n    const orange_fp = fingerprint32(\"orange\");\n    try dyn_cf32.add(orange_h, orange_fp);\n\n    // 2. You can only delete elements that were inserted before.\n    //    Trying to delete a non-existing item has a chance of \n    //    breaking the filter (makes false negatives possible).\n    //    Deleting a non-existing item can either cause the \n    //    deletion of another colliding item or fail to find\n    //    a matching fingerprint in the filter. In the second\n    //    case the filter locks down and returns an error for\n    //    all operations, as it is now impossible to know what\n    //    the correct state would be.\n    dyn_cf32.remove(0, 0) catch |err| switch (err) {\n        error.Broken =\u003e std.debug.warn(\".remove, broken\\n\"),\n    };\n\n    _ = dyn_cf32.is_broken(); // =\u003e true\n    dyn_cf32.add(orange_fp, orange_fp) catch |err| switch (err) {\n        error.Broken =\u003e std.debug.warn(\".add, broken\\n\"),\n        error.TooFull =\u003e {},\n    };\n\n    if (dyn_cf32.count()) |_| {\n        std.debug.warn(\".count, works\\n\"); // won't be printed\n    } else |err| switch (err) {\n        error.Broken =\u003e std.debug.warn(\".count, broken\\n\")\n    }\n\n    // Since searching does not mutate the filter, if the item \n    // is found, no error is returned:\n    _ = try dyn_cf32.maybe_contains(orange_h, orange_fp); // =\u003e true\n\n    // But if an item is not found, we don't know if it was wrongly\n    // deleted or not, so the filter has to return an error in order\n    // to ensure that it does not return a false negative response.\n    if (dyn_cf32.maybe_contains(0, 0)) |_| {\n        std.debug.warn(\".maybe_contains, works\\n\"); // won't be printed\n    } else |err| switch (err) {\n        error.Broken =\u003e std.debug.warn(\".maybe_contains, broken\\n\")\n    }\n    \n    // You should *NEVER* get into that situation. If you do, it's \n    // a programming error. If your program runs in an environment\n    // where a request that involves the filter might be repeated\n    // (e.g. web servers), mark each request by a unique ID and \n    // keep some kind of commit log to ensure you don't run the \n    // same request twice, as it's semantically wrong to expect\n    // idempotence from Cuckoo filter commands.\n\n    //  3. Other small errors could be trying to pass to init memory\n    //     with the wrong alignment or a wrong buffer size. Try to\n    //     use the provided functions (i.e. size_for, size_for_exactly)\n    //     to always have your buffers be the right size. You can\n    //     also use those functions to reason about your data and even\n    //     opt not to use a filter if the tradeoff is not worth it.\n    if (cuckoo.Filter8.init(memory[1..13])) |_| {\n        std.debug.warn(\".init, works\\n\"); // won't be printed\n    } else |err| switch (err) {\n        error.BadLength =\u003e std.debug.warn(\".init failed, use .size_for()!\\n\")\n    }\n\n    //\n    // FIXING TOO FULL\n    //\n\n    // Filter8 and Filter16 have 4 element-wide buckets, \n    // while Filter32 has 2 element-wide buckets.\n    // Each fingerprint has two buckets that can be used to \n    // house it. This means that you can have, respectively, \n    // up to 8 (F8, F16) and 4 (F32) collisions/copies before both \n    // buckets fill completely and you get TooFull. In practice, \n    // you get an extra chance because of how filters work internally. \n    // There's a special slot that houses a single fingerprint that \n    // could not find space in one of its 2 candidate slots. \n    // The problem is that once that \"safety\" slot is filled, the \n    // filter becomes much more succeptible to collisions and is forced\n    // to return TooFull when in fact it could try to make space.\n    // If you are also deleting elements from the filter, and \n    // not just adding them, this is what you can do to try and \n    // recover from that situation.\n\n    // Returns true if the safety slot is occupied. \n    var bad_situation = dyn_cf32.is_toofull();\n    // Note that you might not have ever received a TooFull error for \n    // this function to return true. In our previous example with \n    // dyn_cf32, it took us 5 insertions to obtain a TooFull error. \n    // This function would return true after 4.\n    \n    // Try to fix the situation:\n    if (bad_situation) {\n        dyn_cf32.fix_toofull() catch |err| switch (err) {\n            error.Broken =\u003e {},\n            error.TooFull =\u003e {},\n        };\n    }\n\n    // With this function you can only fix TooFull, not Broken.\n    // If fix_toofull returns TooFull, it means that it failed.\n    // In practice you will need to free more elements before\n    // being able to fix the situation, but in theory calling \n    // the function multiple times might eventually fix the \n    // situation (i.e. it can make progress each call).\n    // That said, going back to practical usage, you are probably\n    // in a problematic situation when it gets to that point.\n    // To ensure you never have to deal with these problems,\n    // make sure you:\n    //    (1) Never overfill/undersize a filter.\n    //    (2) Get entropy right for the fingerprinting function.\n    // \n    // A trick to get (2) right is to pluck it not out of the \n    // original element, but out of hash2(element). Just make sure \n    // you use a different hasing function, independent from the \n    // fitst one, otherwise you're going to still end up with too \n    // little entropy, and be aware of the increased computational \n    // cost. Secondary hashing might be worth it for semi-strucutred\n    // data where you might find it hard to know if you're plucking\n    // \"variable\" data or part of the structure (e.g. JSON data),\n    // since the latter is bound to have lower entropy.\n\n    //\n    // PRNG Stuff\n    //\n\n    // Cuckoo Filters need a random number generator to decide which \n    // fingerprint to evict when a given bucket pair is full. This\n    // library provides a default implementation that uses the Zig \n    // standard library's Xoroshiro implementation, seeded by default to 42.\n\n    // If your application has short-lived sessions, a static seed won't be \n    // good enough, as it will basically result in giving out the same\n    // number over and over again, similarly to what is shown in that one\n    // dilbert strip. To fix that use seed_default_prng:\n    var buf: [8]u8 = undefined;\n    try std.crypto.randomBytes(buf[0..]);\n    const seed = std.mem.readIntSliceLittle(u64, buf[0..8]);\n    cuckoo.seed_default_prng(seed);\n\n    // Additionally, you might also want to provide your own PRNG \n    // implementation, either because you have specific needs (CSPRNG) or\n    // because you might want to make the filter fully deterministic and thus\n    // need to be able to persist and restore the PRNG's state.\n    // You can customize the PRNG of each filter by providing an appropriate\n    // function pointer:\n    dyn_cf32.rand_fn = DilbertRandom;\n\n    // From now on `dyn_cf32` will stop using the default implementation\n    // (shared by default by all filters) and will instead only use the \n    // provided function. If you use this functionality, *make sure to\n    // set the function pointer again when loading the filter from disk*.\n\n    // If you're fine with the default implementation and want more control\n    // than just seeding, use .get_default_prng_state() and \n    // .set_default_prng_state(), but beware that you are modifying a \n    // \"singleton\" struct used by all filters. If you are in a multi-threaded\n    // context this might cause problems if you are executing \n    // .add / .delete / .fix_toofull and altering the prng singleton at the \n    // same time. In that case you will have to customize .rand_fn\n}\n\nfn DilbertRandom() u1 {\n    return 1;\n} \n```\n\nThis will output:\n```\nyep, too full\n.remove, broken\n.add, broken\n.count, broken\n.maybe_contains, broken\n.init failed, use .size_for()!\n```\n\n\nPlanned Features\n----------------\n- (maybe) Full-fledged wrappers for the most common languages? It's not that hard to get\n  there given where I got to with `c-abi-examples`.\n\n\nLicense\n-------\n\nMIT License\n\nCopyright (c) 2019 Loris Cro\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkristoff-it%2Fzig-cuckoofilter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkristoff-it%2Fzig-cuckoofilter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkristoff-it%2Fzig-cuckoofilter/lists"}