Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Column offset #12092

Merged
merged 9 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
- [Allow using `/` to access files inside a directory reached through a data
link.][11926]
- [Added Table.Offset][12071]
- [Added Column.Offset][12092]

[11926]: https://github.com/enso-org/enso/pull/11926
[12071]: https://github.com/enso-org/enso/pull/12071
[12092]: https://github.com/enso-org/enso/pull/12092

#### Enso Language & Runtime

Expand Down
20 changes: 20 additions & 0 deletions distribution/lib/Standard/Database/0.0.0-dev/src/DB_Column.enso
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Standard.Base.Errors.Common.Index_Out_Of_Bounds
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import Standard.Base.Errors.Illegal_State.Illegal_State
import Standard.Base.Internal.Rounding_Helpers
from Standard.Base.Metadata.Widget import Numeric_Input
from Standard.Base.Widget_Helpers import make_data_cleanse_vector_selector, make_format_chooser, make_regex_text_widget

import Standard.Table.Internal.Column_Naming_Helper.Column_Naming_Helper
Expand All @@ -13,6 +14,7 @@ import Standard.Table.Internal.Java_Problems
import Standard.Table.Internal.Problem_Builder.Problem_Builder
import Standard.Table.Internal.Value_Type_Helpers
import Standard.Table.Internal.Widget_Helpers
import Standard.Table.Fill_With.Fill_With
import Standard.Table.Rows_To_Read.Rows_To_Read
from Standard.Table import Auto, Column, Data_Formatter, Previous_Value, Sort_Column, Table, Value_Type
from Standard.Table.Column import default_date_period
Expand Down Expand Up @@ -1320,6 +1322,24 @@ type DB_Column
result = self.is_empty.iif default self
result.rename self.name

## ALIAS shift, lead, lag, slide, displace
GROUP Standard.Base.Values
ICON column_add

Returns a new column offset by n rows, where missing values have been replaced with the provided fill_with strategy.

Arguments:
- n: The number of rows to offset the new column by. Negative n slides the values down in the column, adding records at the start.
Positive n slides the values up in the column, adding records at the end. Defaults to -1.
- fill_with: The value to replace missing values with. Defaults to adding Nothing Values.
- - ..Nothing - Add Nothing values in the spaces created by sliding the existing values.
- - ..Closest_Value - If n is negative the first value gets used, if n is negative the last value gets used.
- - ..Wrap_Around - In this mode values that slide off the top or bottom reappear at the other end. So no values get lost they are just rotated.
@n (self-> Numeric_Input minimum=0-self.length maximum=self.length-1)
offset self n=-1:Integer fill_with:Fill_With=..Nothing -> Column =
_ = [n, fill_with]
Error.throw (Unsupported_Database_Operation.Error "offset")

## GROUP Standard.Base.Metadata
ICON text_input
Returns a new column, containing the same elements as `self`, but with
Expand Down
19 changes: 19 additions & 0 deletions distribution/lib/Standard/Table/0.0.0-dev/src/Column.enso
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ import project.Internal.Column_Naming_Helper.Column_Naming_Helper
import project.Internal.Column_Ops
import project.Internal.Date_Time_Helpers
import project.Internal.Java_Problems
import project.Internal.Offset_Helper
import project.Internal.Parse_Values_Helper
import project.Internal.Read_Many_Helpers
import project.Internal.Storage
import project.Internal.Value_Type_Helpers
import project.Internal.Widget_Helpers
import project.Fill_With.Fill_With
import project.Rows_To_Read.Rows_To_Read
import project.Table.Table
import project.Value_Type.Auto
Expand Down Expand Up @@ -2608,6 +2610,23 @@ type Column
data = Statistic.running self.to_vector statistic
Column.from_vector name data

## ALIAS shift, lead, lag, slide, displace
GROUP Standard.Base.Values
ICON column_add

Returns a new column offset by n rows, where missing values have been replaced with the provided fill_with strategy.

Arguments:
- n: The number of rows to offset the new column by. Negative n slides the values down in the column, adding records at the start.
Positive n slides the values up in the column, adding records at the end. Defaults to -1.
- fill_with: The value to replace missing values with. Defaults to adding Nothing Values.
- - ..Nothing - Add Nothing values in the spaces created by sliding the existing values.
- - ..Closest_Value - If n is negative the first value gets used, if n is negative the last value gets used.
- - ..Wrap_Around - In this mode values that slide off the top or bottom reappear at the other end. So no values get lost they are just rotated.
@n (self-> Numeric_Input minimum=0-self.length maximum=self.length)
offset self n=-1:Integer fill_with:Fill_With=..Nothing -> Column =
Offset_Helper.column_offset_impl self n fill_with

## PRIVATE
pretty : Text
pretty self =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@ table_offset_impl table:Table (columns:(Vector | Text | Integer | Regex)=[]) n:I
new_columns = new_names.zip new_storages.to_vector new_name-> new_storage -> Column.from_storage new_name new_storage
new_columns.fold table t-> c->
t.set c c.name set_mode

column_offset_impl column:Column n:Integer=-1 fillWith:Fill_With=..Nothing =
java_column = column.java_column
fillWith_java = fillWith.to_java
new_storage = Java_Offset.offset_single_column java_column n fillWith_java
new_name = Column_Naming_Helper.in_memory.function_name "offset" [column, n, fillWith]
Column.from_storage new_name new_storage
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ public Value visitUnaryNot(ExpressionParser.UnaryNotContext ctx) {

@Override
public Value visitUnaryMinus(ExpressionParser.UnaryMinusContext ctx) {
return executeMethod("*", visit(ctx.expr()), Value.asValue(-1));
var v = visit(ctx.expr());
if (v.isNumber() && v.asDouble() == v.asLong()) {
return Value.asValue(Math.negateExact(v.asLong()));
}
return executeMethod("*", v, Value.asValue(-1));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.stream.IntStream;
import org.enso.table.data.column.storage.Storage;
import org.enso.table.data.mask.OrderMask;
import org.enso.table.data.table.Column;
Expand All @@ -19,19 +20,69 @@ public static Storage<?>[] offset(
ProblemAggregator problemAggregator) {
if (n == 0 || sourceColumns.length == 0)
return Arrays.stream(sourceColumns).map(c -> c.getStorage()).toArray(Storage<?>[]::new);
var offsetRowVisitorFactory = new OffsetRowVisitorFactory(sourceColumns[0], n, fillWith);
var rowOrderMask =
groupingColumns.length == 0 && orderingColumns.length == 0
? calculate_ungrouped_unordered_mask(sourceColumns[0].getSize(), n, fillWith)
: calculate_grouped_ordered_mask(
sourceColumns[0].getSize(),
n,
fillWith,
groupingColumns,
orderingColumns,
directions,
problemAggregator);
return Arrays.stream(sourceColumns)
.map(c -> c.getStorage().applyMask(OrderMask.fromArray(rowOrderMask)))
.toArray(Storage<?>[]::new);
}

public static Storage<?> offset_single_column(Column sourceColumn, int n, FillWith fillWith) {
if (n == 0) return sourceColumn.getStorage();
var rowOrderMask = calculate_ungrouped_unordered_mask(sourceColumn.getSize(), n, fillWith);
return sourceColumn.getStorage().applyMask(OrderMask.fromArray(rowOrderMask));
}

private static int[] calculate_ungrouped_unordered_mask(int numRows, int n, FillWith fillWith) {
return IntStream.range(0, numRows)
.map(i -> calculate_row_offset(i, n, fillWith, numRows))
.toArray();
}

private static int calculate_row_offset(int rowIndex, int n, FillWith fillWith, int numRows) {
int result = rowIndex + n;
if (result < 0) {
return switch (fillWith) {
case NOTHING -> Storage.NOT_FOUND_INDEX;
case CLOSEST_VALUE -> 0;
case WRAP_AROUND -> (result % numRows) == 0 ? 0 : (result % numRows) + numRows;
};
} else if (result >= numRows) {
return switch (fillWith) {
case NOTHING -> Storage.NOT_FOUND_INDEX;
case CLOSEST_VALUE -> numRows - 1;
AdRiley marked this conversation as resolved.
Show resolved Hide resolved
case WRAP_AROUND -> result % numRows;
};
}
return result;
}

private static int[] calculate_grouped_ordered_mask(
int numRows,
int n,
FillWith fillWith,
Column[] groupingColumns,
Column[] orderingColumns,
int[] directions,
ProblemAggregator problemAggregator) {
var offsetRowVisitorFactory = new OffsetRowVisitorFactory(numRows, n, fillWith);
GroupingOrderingVisitor.visit(
groupingColumns,
orderingColumns,
directions,
problemAggregator,
offsetRowVisitorFactory,
sourceColumns[0].getSize());
return Arrays.stream(sourceColumns)
.map(
c ->
c.getStorage().applyMask(OrderMask.fromArray(offsetRowVisitorFactory.rowOrderMask)))
.toArray(Storage<?>[]::new);
numRows);
return offsetRowVisitorFactory.rowOrderMask;
}

private static class OffsetRowVisitorFactory implements RowVisitorFactory {
Expand All @@ -40,8 +91,8 @@ private static class OffsetRowVisitorFactory implements RowVisitorFactory {
int n;
FillWith fillWith;

OffsetRowVisitorFactory(Column sourceColumn, int n, FillWith fillWith) {
rowOrderMask = new int[sourceColumn.getSize()];
OffsetRowVisitorFactory(int numRows, int n, FillWith fillWith) {
rowOrderMask = new int[numRows];
this.n = n;
this.fillWith = fillWith;
}
Expand Down
33 changes: 28 additions & 5 deletions test/Table_Tests/src/Common_Table_Operations/Offset_Spec.enso
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from Standard.Base import all
from Standard.Test import all
from Standard.Table import all

from Standard.Database.Errors import Unsupported_Database_Operation
import Standard.Database.Feature.Feature
Expand All @@ -19,6 +20,9 @@ add_specs suite_builder setup =
t = table_builder [["ix", [1, 2, 3, 4, 5]], ["X", [100, 3, Nothing, 4, 12]], ["Y", [100, 4, 2, Nothing, 11]]]
t2 = t.offset ["X"]
t2.should_fail_with (Unsupported_Database_Operation.Error "offset")
c = t.at 0
c2 = c.offset
c2.should_fail_with (Unsupported_Database_Operation.Error "offset")

add_offset_specs suite_builder setup =
prefix = setup.prefix
Expand All @@ -27,11 +31,11 @@ add_offset_specs suite_builder setup =
c = t.at 0
c_nothings = t.at 1
c_zero_rows = t.take 0 . at 0
suite_builder.group prefix+"Column.Offset with default fill strategy" pending="TODO Column.Offset" group_builder->
suite_builder.group prefix+"Column.Offset with default fill strategy" group_builder->
group_builder.specify "Works with default values" <|
r = c.offset
r.to_vector . should_equal [Nothing, 1, 2]
r.name . should_equal c.name
r.name . should_equal "offset([A], -1, Fill_With.Nothing)"
group_builder.specify "Negative n shifts the values down" <|
r = c.offset -1
r.to_vector . should_equal [Nothing, 1, 2]
Expand All @@ -50,7 +54,7 @@ add_offset_specs suite_builder setup =
group_builder.specify "Works with zero rows" <|
r = c_zero_rows.offset
r.to_vector . should_equal []
suite_builder.group prefix+"Column.Offset with closest value fill strategy" pending="TODO Column.Offset" group_builder->
suite_builder.group prefix+"Column.Offset with closest value fill strategy" group_builder->
group_builder.specify "Negative n shifts the values down" <|
r = c.offset -1 ..Closest_Value
r.to_vector . should_equal [1, 1, 2]
Expand All @@ -75,7 +79,7 @@ add_offset_specs suite_builder setup =
group_builder.specify "Works with positive n and column of nothings" <|
r = c_nothings.offset 1 ..Closest_Value
r.to_vector . should_equal [Nothing, Nothing, Nothing]
suite_builder.group prefix+"Column.Offset with wrap around fill strategy" pending="TODO Column.Offset" group_builder->
suite_builder.group prefix+"Column.Offset with wrap around fill strategy" group_builder->
group_builder.specify "Negative n shifts the values down" <|
r = c.offset -1 ..Wrap_Around
r.to_vector . should_equal [3, 1, 2]
Expand Down Expand Up @@ -106,7 +110,7 @@ add_offset_specs suite_builder setup =
group_builder.specify "Works with positive n and column of nothings" <|
r = c_nothings.offset 1 ..Wrap_Around
r.to_vector . should_equal [Nothing, Nothing, Nothing]
suite_builder.group prefix+"Column.Offset with constant fill strategy" pending="TODO Column.Offset" group_builder->
suite_builder.group prefix+"Column.Offset with constant fill strategy" pending="TODO - constant fill strategy" group_builder->
group_builder.specify "Negative n shifts the values down" <|
r = c.offset -1 42
r.to_vector . should_equal [42, 1, 2]
Expand Down Expand Up @@ -134,6 +138,25 @@ add_offset_specs suite_builder setup =
group_builder.specify "Can create mixed colums" <|
r = c.offset -1 "42"
r.to_vector . should_equal ["42", 1, 2]
suite_builder.group prefix+"Works in Table.set expressions with default" group_builder->
group_builder.specify "Works with default values" <|
r = t.set (expr 'offset([A])') . at "offset([A])"
r.to_vector . should_equal [Nothing, 1, 2]
group_builder.specify "Negative n shifts the values down" <|
r = t.set (expr 'offset([A], -1)') . at "offset([A], -1)"
r.to_vector . should_equal [Nothing, 1, 2]
group_builder.specify "Positive n shifts the values up" <|
r = t.set (expr 'offset([A], 1)') . at "offset([A], 1)"
r.to_vector . should_equal [2, 3, Nothing]
group_builder.specify "Zero n is a no-op" <|
r = t.set (expr 'offset([A], 0)') . at "offset([A], 0)"
r.to_vector . should_equal [1, 2, 3]
group_builder.specify "Large negative n values work" <|
r = t.set (expr 'offset([A], -1024)') . at "offset([A], -1024)"
r.to_vector . should_equal [Nothing, Nothing, Nothing]
group_builder.specify "Large positive n values work" <|
r = t.set (expr 'offset([A], 1024)') . at "offset([A], 1024)"
r.to_vector . should_equal [Nothing, Nothing, Nothing]
suite_builder.group prefix+"Table.Offset with default fill strategy" group_builder->
group_builder.specify "Works with default values" <|
r = t.offset ["A"]
Expand Down
Loading