Add RecordSeries::get_record and support for (my_ticks 0)
This commit introduces the `get_record` method to `RecordSeries`, allowing for the dynamic reconstruction of a full record from its constituent fields at a given index. This enables idiomatic access to series data using indexing like `(my_ticks 0)`. The `push-series` function has also been updated to correctly handle the structure of records when pushing data into a `RecordSeries`.
This commit is contained in:
@@ -1,15 +1,15 @@
|
|||||||
;; Output: 26.7
|
;; Output: 26.7
|
||||||
;; Benchmark: 998ns
|
;; Benchmark: 998ns
|
||||||
;; Benchmark-Repeat: 2047
|
;; Benchmark-Repeat: 2047
|
||||||
(do
|
(do
|
||||||
(def template {:price 10.0 :volume 100})
|
(def template {:price 10.0 :volume 100 :msg "hi"})
|
||||||
(def my_ticks (create-series template))
|
(def my_ticks (create-series template))
|
||||||
|
|
||||||
(push-series my_ticks {:price 10.5 :volume 100})
|
(push-series my_ticks {:price 10.5 :volume 100 :msg "A"})
|
||||||
(push-series my_ticks {:price 11.2 :volume 200})
|
(push-series my_ticks {:price 11.2 :volume 200 :msg "B"})
|
||||||
(push-series my_ticks {:price 15.5 :volume 300})
|
(push-series my_ticks {:price 15.5 :volume 300 :msg "C"})
|
||||||
|
|
||||||
(def prices (.price my_ticks))
|
(def prices (.price my_ticks))
|
||||||
|
|
||||||
(+ (prices 0) (prices 1))
|
(+ (prices 0) (prices 1))
|
||||||
)
|
)
|
||||||
|
|||||||
+34
-11
@@ -251,6 +251,25 @@ impl RecordSeries {
|
|||||||
pub fn field(&self, key: Keyword) -> Option<Rc<std::cell::RefCell<dyn SeriesMember>>> {
|
pub fn field(&self, key: Keyword) -> Option<Rc<std::cell::RefCell<dyn SeriesMember>>> {
|
||||||
self.layout.index_of(key).map(|idx| self.fields[idx].clone())
|
self.layout.index_of(key).map(|idx| self.fields[idx].clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dynamically reconstructs the full Record at the given lookback index.
|
||||||
|
/// This is an O(N) operation where N is the number of fields, meaning it breaks
|
||||||
|
/// the 0-Copy performance path, but it's essential for idiomatic AST access `(series 0)`.
|
||||||
|
pub fn get_record(&self, index: usize) -> Option<Value> {
|
||||||
|
if self.fields.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut vals = Vec::with_capacity(self.fields.len());
|
||||||
|
for field in &self.fields {
|
||||||
|
match field.borrow().get_value(index) {
|
||||||
|
Some(v) => vals.push(v),
|
||||||
|
None => return None, // Out of bounds or incomplete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Value::Record(self.layout.clone(), Rc::new(vals)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for RecordSeries {
|
impl Debug for RecordSeries {
|
||||||
@@ -356,19 +375,23 @@ pub fn register(env: &Environment) {
|
|||||||
}
|
}
|
||||||
let (series_val, record_val) = (&args[0], &args[1]);
|
let (series_val, record_val) = (&args[0], &args[1]);
|
||||||
|
|
||||||
if let Value::Object(obj) = series_val {
|
let record_series = if let Value::Object(obj) = series_val {
|
||||||
if let Some(record_series) = obj.as_any().downcast_ref::<RecordSeries>() {
|
obj.as_any().downcast_ref::<RecordSeries>()
|
||||||
if let Value::Record(_, values) = record_val {
|
} else {
|
||||||
// Push each value into its corresponding column (SoA push)
|
None
|
||||||
for (i, field_val) in values.iter().enumerate() {
|
};
|
||||||
if let Some(field_series) = record_series.fields.get(i) {
|
|
||||||
field_series.borrow_mut().push_value(field_val.clone(), None);
|
if let Some(record_series) = record_series {
|
||||||
}
|
if let Value::Record(_, values) = record_val {
|
||||||
|
// Push each value into its corresponding column (SoA push)
|
||||||
|
for (i, field_val) in values.iter().enumerate() {
|
||||||
|
if let Some(field_series) = record_series.fields.get(i) {
|
||||||
|
field_series.borrow_mut().push_value(field_val.clone(), None);
|
||||||
}
|
}
|
||||||
return Value::Void;
|
|
||||||
} else {
|
|
||||||
panic!("push-series expects a record as the second argument");
|
|
||||||
}
|
}
|
||||||
|
return Value::Void;
|
||||||
|
} else {
|
||||||
|
panic!("push-series expects a record as the second argument");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic!("push-series expects a RecordSeries as the first argument")
|
panic!("push-series expects a RecordSeries as the first argument")
|
||||||
|
|||||||
@@ -547,6 +547,24 @@ impl VM {
|
|||||||
} else {
|
} else {
|
||||||
break Err("Series index must be an integer".to_string());
|
break Err("Series index must be an integer".to_string());
|
||||||
}
|
}
|
||||||
|
} else if let Some(rs) = obj.as_any().downcast_ref::<crate::ast::rtl::series::RecordSeries>() {
|
||||||
|
// Support for `(my_ticks 0)` - reconstructs the full Record from SoA!
|
||||||
|
if arg_vals.len() != 1 {
|
||||||
|
break Err("RecordSeries indexer expects exactly 1 argument (the lookback index)".to_string());
|
||||||
|
}
|
||||||
|
if let Value::Int(idx) = arg_vals[0] {
|
||||||
|
if idx < 0 {
|
||||||
|
break Err("RecordSeries lookback index cannot be negative".to_string());
|
||||||
|
}
|
||||||
|
if let Some(rec) = rs.get_record(idx as usize) {
|
||||||
|
break Ok(rec);
|
||||||
|
} else {
|
||||||
|
// Out of bounds / not enough data yet
|
||||||
|
break Ok(Value::Void);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break Err("RecordSeries index must be an integer".to_string());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
break Err(format!("Object is not callable: {}", obj.type_name()));
|
break Err(format!("Object is not callable: {}", obj.type_name()));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user