use super::funejson::Value; /// Largest integer JavaScript can hold exactly; beyond it the three languages disagree. const SAFE_INTEGER: i64 = 9007199254740991; /// The name of the group a value belongs to. /// /// Each language has its own default string conversion and they disagree /// (Python prints True where JavaScript prints true, and 1.0 where JavaScript /// prints 1), so the rendering is spelled out here instead of inherited. /// /// # Panics /// Panics on a float, a list or a map, or an integer outside the safe range. fn group_name_of(value: &Value, key: &str) -> String { match value { // Absent and null are the same thing: `Value::get` returns Null for a // missing field, and a document that nulls a field means the same. Value::Null => String::new(), Value::Str(s) => s.clone(), Value::Bool(b) => (if *b { "true" } else { "false" }).to_string(), Value::Int(i) => { if i.abs() > SAFE_INTEGER { panic!("cannot group by the out-of-range number {} at \"{}\"", i, key); } i.to_string() } Value::Float(f) => panic!("cannot group by the fractional number {} at \"{}\"", f, key), _ => panic!("cannot group by the list or map at \"{}\"", key), } } /// Group `records` by the value at `key`. /// /// Records keep their input order inside each group, so grouping a sorted list /// never unsorts it - the property callers actually depend on. /// /// Records are `Value` because they are open JSON maps; a struct would be a /// lie about data whose shape differs from one record to the next. The result /// is the groups in first-appearance order, as `map` is in Rust. pub fn group_by_key(records: &[Value], key: &str) -> Vec<(String, Vec)> { let mut groups: Vec<(String, Vec)> = Vec::new(); for record in records { let name = group_name_of(record.get(key), key); // Linear scan rather than a hash map: it keeps first-appearance order // without a second pass, and group counts are small in practice. match groups.iter_mut().find(|(existing, _)| *existing == name) { Some((_, bucket)) => bucket.push(record.clone()), None => groups.push((name, vec![record.clone()])), } } groups } /// How many records fall in each group, without carrying the records themselves. pub fn count_by_key(records: &[Value], key: &str) -> Vec<(String, i64)> { group_by_key(records, key) .into_iter() .map(|(name, bucket)| (name, bucket.len() as i64)) .collect() } pub fn fune_vector(args: &[Value]) -> Value { // Refuse what the typed signature cannot hold, with the wording TypeScript // and Python use, rather than let the conversion below quietly change it. if args[1].as_str().is_empty() { panic!("groupByKey needs a non-empty key name"); } Value::Obj( group_by_key(args[0].as_arr(), args[1].as_str()) .into_iter() .map(|(name, bucket)| (name, Value::Arr(bucket))) .collect(), ) }