Skip to content

Commit

Permalink
feat: add $distinct function (#59)
Browse files Browse the repository at this point in the history
* feat: add $distinct function

* chore: allow mutable key for hash-set, merge test cases
  • Loading branch information
vartanbeno authored Aug 5, 2024
1 parent 7e11a29 commit 227b5ac
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ hashbrown = "0.14.5"
dtoa = "1.0.9"
base64 = "0.22.1"
serde_json = "1.0.117"
rand = "0.8.5"

[dev-dependencies]
test-case = "3.3.1"
Expand Down
26 changes: 26 additions & 0 deletions src/evaluator/functions.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use base64::Engine;
use std::borrow::Borrow;
use std::collections::HashSet;

use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
Expand Down Expand Up @@ -997,6 +998,31 @@ pub fn fn_reverse<'a>(
Ok(result)
}

#[allow(clippy::mutable_key_type)]
pub fn fn_distinct<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
) -> Result<&'a Value<'a>> {
max_args!(context, args, 1);

let arr = args.first().copied().unwrap_or_else(Value::undefined);
if !arr.is_array() || arr.len() <= 1 {
return Ok(arr);
}

let result = Value::array_with_capacity(context.arena, arr.len(), ArrayFlags::empty());
let mut set = HashSet::new();
for member in arr.members() {
if set.contains(member) {
continue;
}
set.insert(member);
result.push(member);
}

Ok(result)
}

pub fn fn_join<'a>(
context: FunctionContext<'a, '_>,
args: &[&'a Value<'a>],
Expand Down
40 changes: 39 additions & 1 deletion src/evaluator/value/impls.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::ops::Index;
use std::{
hash::{Hash, Hasher},
ops::Index,
};

use rand::Rng;

use super::Value;

Expand Down Expand Up @@ -99,3 +104,36 @@ impl std::fmt::Display for Value<'_> {
write!(f, "{:#?}", self)
}
}

impl Hash for Value<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Value::Undefined => 0.hash(state),
Value::Null => 1.hash(state),
Value::Number(n) => n.to_bits().hash(state),
Value::Bool(b) => b.hash(state),
Value::String(s) => s.hash(state),
Value::Array(a, _) => a.hash(state),
Value::Object(map) => {
let mut keys_sorted = map.keys().collect::<Vec<_>>();
keys_sorted.sort();

for key in keys_sorted {
key.hash(state);
map.get(key).hash(state);
}
}
Value::Range(r) => r.hash(state),
Value::Lambda { .. } => generate_random_hash(state),
Value::NativeFn { name, .. } => name.hash(state),
Value::Transformer { .. } => generate_random_hash(state),
}
}
}

impl Eq for Value<'_> {}

fn generate_random_hash<H: Hasher>(state: &mut H) {
let random_number: u64 = rand::thread_rng().gen();
random_number.hash(state);
}
12 changes: 11 additions & 1 deletion src/evaluator/value/range.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::ops::Index;
use std::{
hash::{Hash, Hasher},
ops::Index,
};

use bumpalo::Bump;

Expand Down Expand Up @@ -61,6 +64,13 @@ impl<'a> PartialEq<Range<'_>> for Range<'a> {
}
}

impl Hash for Range<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.start.hash(state);
self.end.hash(state);
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ impl<'a> JsonAta<'a> {
bind_native!("ceil", 1, fn_ceil);
bind_native!("contains", 2, fn_contains);
bind_native!("count", 1, fn_count);
bind_native!("distinct", 1, fn_distinct);
bind_native!("each", 2, fn_each);
bind_native!("error", 1, fn_error);
bind_native!("exists", 1, fn_exists);
Expand Down
32 changes: 32 additions & 0 deletions tests/testsuite/groups/function-distinct/extra.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"expr": "$distinct(true)",
"data": null,
"bindings": {},
"result": true
},
{
"expr": "$distinct([1..4])",
"data": null,
"bindings": {},
"result": [1, 2, 3, 4]
},
{
"expr": "$distinct([[1, 2], [1, 2, 3], [1, 2]])",
"data": null,
"bindings": {},
"result": [[1, 2], [1, 2, 3]]
},
{
"expr": "$distinct([\"foo\", \"bar\", \"foo\", \"baz\", \"bar\"])",
"data": null,
"bindings": {},
"result": ["foo", "bar", "baz"]
},
{
"expr": "$distinct([[1..4, 5..10], [1..7, 8..10]])",
"data": null,
"bindings": {},
"result": [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
}
]

0 comments on commit 227b5ac

Please sign in to comment.