From 455e8ac64de1162fb9afba62e6f3802dd9f6f7a8 Mon Sep 17 00:00:00 2001 From: Jun Tan Date: Fri, 28 Feb 2020 15:00:24 -0600 Subject: [PATCH 1/3] add eq, neq, gt, lt, is computed column calculations --- cpp/perspective/src/cpp/computed.cpp | 30 ++ cpp/perspective/src/cpp/computed_function.cpp | 70 ++++ cpp/perspective/src/cpp/emscripten.cpp | 5 + .../src/include/perspective/computed.h | 5 + .../include/perspective/computed_function.h | 18 +- .../src/js/computed_column.js | 45 +++ packages/perspective/src/js/perspective.js | 10 + packages/perspective/test/js/computed.js | 382 ++++++++++++++++++ 8 files changed, 564 insertions(+), 1 deletion(-) diff --git a/cpp/perspective/src/cpp/computed.cpp b/cpp/perspective/src/cpp/computed.cpp index 6bdb8fd53f..1472977792 100644 --- a/cpp/perspective/src/cpp/computed.cpp +++ b/cpp/perspective/src/cpp/computed.cpp @@ -116,6 +116,10 @@ t_computed_column::get_computed_function_1(t_computation computation) { case MULTIPLY: return computed_function::multiply; \ case DIVIDE: return computed_function::divide; \ case PERCENT_A_OF_B: return computed_function::percent_of; \ + case EQUALS: return computed_function::equals; \ + case NOT_EQUALS: return computed_function::not_equals; \ + case GREATER_THAN: return computed_function::greater_than; \ + case LESS_THAN: return computed_function::less_than; \ default: break; \ } @@ -152,6 +156,12 @@ t_computed_column::get_computed_function_2(t_computation computation) { case DTYPE_FLOAT64: { GET_COMPUTED_FUNCTION_2(DTYPE_FLOAT64); } break; + case DTYPE_STR: { + switch (computation.m_name) { + case IS: return computed_function::is; + default: break; + } + } default: break; } @@ -302,6 +312,7 @@ void t_computed_column::make_computations() { std::vector dtypes = {DTYPE_FLOAT64, DTYPE_FLOAT32, DTYPE_INT64, DTYPE_INT32, DTYPE_INT16, DTYPE_INT8, DTYPE_UINT64, DTYPE_UINT32, DTYPE_UINT16, DTYPE_UINT8}; std::vector numeric_function_1 = {INVERT, POW, SQRT, ABS, BUCKET_10, BUCKET_100, BUCKET_1000, BUCKET_0_1, BUCKET_0_0_1, BUCKET_0_0_0_1}; std::vector numeric_function_2 = {ADD, SUBTRACT, MULTIPLY, DIVIDE, PERCENT_A_OF_B}; + std::vector numeric_comparison_2 = {EQUALS, NOT_EQUALS, GREATER_THAN, LESS_THAN}; for (const auto f : numeric_function_1) { for (auto i = 0; i < dtypes.size(); ++i) { @@ -329,6 +340,20 @@ void t_computed_column::make_computations() { } } + for (const auto f : numeric_comparison_2) { + for (auto i = 0; i < dtypes.size(); ++i) { + for (auto j = 0; j < dtypes.size(); ++j) { + t_computed_column::computations.push_back( + t_computation{ + f, + std::vector{dtypes[i], dtypes[j]}, + DTYPE_BOOL + } + ); + } + } + } + // Generate string functions std::vector string_function_1 = {UPPERCASE, LOWERCASE}; std::vector string_function_2 = {CONCAT_SPACE, CONCAT_COMMA}; @@ -358,6 +383,11 @@ void t_computed_column::make_computations() { t_computation{LENGTH, std::vector{DTYPE_STR}, DTYPE_INT64} ); + // IS takes 2 strings and returns a bool + t_computed_column::computations.push_back( + t_computation{IS, std::vector{DTYPE_STR, DTYPE_STR}, DTYPE_BOOL} + ); + // Generate date/datetime functions std::vector date_dtypes = {DTYPE_DATE, DTYPE_TIME}; diff --git a/cpp/perspective/src/cpp/computed_function.cpp b/cpp/perspective/src/cpp/computed_function.cpp index 7d3c1c4b10..a5ae0f9858 100644 --- a/cpp/perspective/src/cpp/computed_function.cpp +++ b/cpp/perspective/src/cpp/computed_function.cpp @@ -301,11 +301,62 @@ NUMERIC_FUNCTION_1(BUCKET_0_0_0_1); return rval; \ } +#define EQUALS(T1, T2) \ + t_tscalar equals_##T1##_##T2(t_tscalar x, t_tscalar y) { \ + t_tscalar rval; \ + rval.set(false); \ + if ((x.is_none() || !x.is_valid()) \ + && (y.is_none() || !y.is_valid())) { \ + rval.set(true); \ + return rval; \ + } else if ((x.is_none() || !x.is_valid()) \ + || (y.is_none() || !y.is_valid())) { \ + rval.set(false); \ + return rval; \ + } \ + rval.set(static_cast(x.get() == y.get())); \ + return rval; \ + } + +#define NOT_EQUALS(T1, T2) \ + t_tscalar not_equals_##T1##_##T2(t_tscalar x, t_tscalar y) { \ + t_tscalar rval; \ + rval.set(false); \ + if ((x.is_none() || !x.is_valid()) \ + || (y.is_none() || !y.is_valid())) return rval; \ + rval.set(static_cast(x.get() != y.get())); \ + return rval; \ + } + +#define GREATER_THAN(T1, T2) \ + t_tscalar greater_than_##T1##_##T2(t_tscalar x, t_tscalar y) { \ + t_tscalar rval; \ + rval.set(false); \ + if ((x.is_none() || !x.is_valid()) \ + || (y.is_none() || !y.is_valid())) return rval; \ + rval.set(static_cast(x.get() > y.get())); \ + return rval; \ + } + +#define LESS_THAN(T1, T2) \ + t_tscalar less_than_##T1##_##T2(t_tscalar x, t_tscalar y) { \ + t_tscalar rval; \ + rval.set(false); \ + if ((x.is_none() || !x.is_valid()) \ + || (y.is_none() || !y.is_valid())) return rval; \ + rval.set(static_cast(x.get() < y.get())); \ + return rval; \ + } + NUMERIC_FUNCTION_2(ADD); NUMERIC_FUNCTION_2(SUBTRACT); NUMERIC_FUNCTION_2(MULTIPLY); NUMERIC_FUNCTION_2(DIVIDE); NUMERIC_FUNCTION_2(PERCENT_OF); +NUMERIC_FUNCTION_2(EQUALS); +NUMERIC_FUNCTION_2(NOT_EQUALS); +NUMERIC_FUNCTION_2(GREATER_THAN); +NUMERIC_FUNCTION_2(LESS_THAN); /** * @brief Generate dispatch functions that call the correct computation method @@ -351,6 +402,10 @@ NUMERIC_FUNCTION_2_DISPATCH_ALL_TYPES(subtract); NUMERIC_FUNCTION_2_DISPATCH_ALL_TYPES(multiply); NUMERIC_FUNCTION_2_DISPATCH_ALL_TYPES(divide); NUMERIC_FUNCTION_2_DISPATCH_ALL_TYPES(percent_of); +NUMERIC_FUNCTION_2_DISPATCH_ALL_TYPES(equals); +NUMERIC_FUNCTION_2_DISPATCH_ALL_TYPES(not_equals); +NUMERIC_FUNCTION_2_DISPATCH_ALL_TYPES(greater_than); +NUMERIC_FUNCTION_2_DISPATCH_ALL_TYPES(less_than); // String functions t_tscalar length(t_tscalar x) { @@ -365,6 +420,21 @@ t_tscalar length(t_tscalar x) { return rval; } +t_tscalar is(t_tscalar x, t_tscalar y) { + t_tscalar rval; + rval.set(false); + + if ((x.is_none() || !x.is_valid() || x.get_dtype() != DTYPE_STR) + || (y.is_none() || !y.is_valid() || y.get_dtype() != DTYPE_STR)) { + return rval; + } + + std::string x_str = x.to_string(); + std::string y_str = y.to_string(); + rval.set(x_str == y_str); + return rval; +} + void uppercase(t_tscalar x, std::int32_t idx, std::shared_ptr output_column) { if (x.is_none() || !x.is_valid() || x.get_dtype() != DTYPE_STR) { output_column->set_scalar(idx, mknone()); diff --git a/cpp/perspective/src/cpp/emscripten.cpp b/cpp/perspective/src/cpp/emscripten.cpp index feea903d24..e6a28fd6cc 100644 --- a/cpp/perspective/src/cpp/emscripten.cpp +++ b/cpp/perspective/src/cpp/emscripten.cpp @@ -2007,9 +2007,14 @@ EMSCRIPTEN_BINDINGS(perspective) { .value("SQRT", SQRT) .value("ABS", ABS) .value("PERCENT_A_OF_B", PERCENT_A_OF_B) + .value("EQUALS", EQUALS) + .value("NOT_EQUALS", NOT_EQUALS) + .value("GREATER_THAN", GREATER_THAN) + .value("LESS_THAN", LESS_THAN) .value("UPPERCASE", UPPERCASE) .value("LOWERCASE", LOWERCASE) .value("LENGTH", LENGTH) + .value("IS", IS) .value("CONCAT_SPACE", CONCAT_SPACE) .value("CONCAT_COMMA", CONCAT_COMMA) .value("BUCKET_10", BUCKET_10) diff --git a/cpp/perspective/src/include/perspective/computed.h b/cpp/perspective/src/include/perspective/computed.h index 6d0f9ee3f7..79cde28a5c 100644 --- a/cpp/perspective/src/include/perspective/computed.h +++ b/cpp/perspective/src/include/perspective/computed.h @@ -36,9 +36,14 @@ enum t_computed_function_name { SQRT, ABS, PERCENT_A_OF_B, + EQUALS, + NOT_EQUALS, + GREATER_THAN, + LESS_THAN, UPPERCASE, LOWERCASE, LENGTH, + IS, CONCAT_SPACE, CONCAT_COMMA, BUCKET_10, diff --git a/cpp/perspective/src/include/perspective/computed_function.h b/cpp/perspective/src/include/perspective/computed_function.h index 7902a9f71a..0ae11d5184 100644 --- a/cpp/perspective/src/include/perspective/computed_function.h +++ b/cpp/perspective/src/include/perspective/computed_function.h @@ -73,6 +73,18 @@ t_tscalar divide(t_tscalar x, t_tscalar y); template t_tscalar percent_of(t_tscalar x, t_tscalar y); +template +t_tscalar equals(t_tscalar x, t_tscalar y); + +template +t_tscalar not_equals(t_tscalar x, t_tscalar y); + +template +t_tscalar greater_than(t_tscalar x, t_tscalar y); + +template +t_tscalar less_than(t_tscalar x, t_tscalar y); + #define NUMERIC_FUNCTION_2_HEADER(NAME) \ template <> t_tscalar NAME(t_tscalar x, t_tscalar y); \ template <> t_tscalar NAME(t_tscalar x, t_tscalar y); \ @@ -89,10 +101,14 @@ NUMERIC_FUNCTION_2_HEADER(add); NUMERIC_FUNCTION_2_HEADER(subtract); NUMERIC_FUNCTION_2_HEADER(multiply); NUMERIC_FUNCTION_2_HEADER(divide); -NUMERIC_FUNCTION_2_HEADER(percent_of); +NUMERIC_FUNCTION_2_HEADER(equals); +NUMERIC_FUNCTION_2_HEADER(not_equals); +NUMERIC_FUNCTION_2_HEADER(greater_than); +NUMERIC_FUNCTION_2_HEADER(less_than); // String functions t_tscalar length(t_tscalar x); +t_tscalar is(t_tscalar x, t_tscalar y); // Functions that return a string/write into a string column should not return, // and instead write directly into the output column. This prevents pointers to diff --git a/packages/perspective-viewer/src/js/computed_column.js b/packages/perspective-viewer/src/js/computed_column.js index d0cd51e922..67a702b547 100644 --- a/packages/perspective-viewer/src/js/computed_column.js +++ b/packages/perspective-viewer/src/js/computed_column.js @@ -229,6 +229,42 @@ export const COMPUTATIONS = { ["Math"], 2 ), + equals: new Computation( + "==", + (x, y) => `(${x} == ${y})`, + "float", + "float", + (a, b) => a === b, + ["Math"], + 2 + ), + not_equals: new Computation( + "!=", + (x, y) => `(${x} != ${y})`, + "float", + "boolean", + (a, b) => a !== b, + ["Math"], + 2 + ), + greater_than: new Computation( + ">", + (x, y) => `(${x} > ${y})`, + "float", + "boolean", + (a, b) => a > b, + ["Math"], + 2 + ), + less_than: new Computation( + "<", + (x, y) => `(${x} < ${y})`, + "float", + "boolean", + (a, b) => a < b, + ["Math"], + 2 + ), uppercase: new Computation( "Uppercase", x => `uppercase(${x})`, @@ -253,6 +289,15 @@ export const COMPUTATIONS = { x => x.length, ["Text"] ), + is: new Computation( + "is", + (x, y) => `(${x} is ${y})`, + "string", + "boolean", + (x, y) => x === y, + ["Text"], + 2 + ), concat_space: new Computation( "concat_space", x => `concat_space(${x})`, diff --git a/packages/perspective/src/js/perspective.js b/packages/perspective/src/js/perspective.js index 26785a503f..2ad827cdfe 100644 --- a/packages/perspective/src/js/perspective.js +++ b/packages/perspective/src/js/perspective.js @@ -67,12 +67,22 @@ export default function(Module) { return __MODULE__.t_computed_function_name.ABS; case "%": return __MODULE__.t_computed_function_name.PERCENT_A_OF_B; + case "==": + return __MODULE__.t_computed_function_name.EQUALS; + case "!=": + return __MODULE__.t_computed_function_name.NOT_EQUALS; + case ">": + return __MODULE__.t_computed_function_name.GREATER_THAN; + case "<": + return __MODULE__.t_computed_function_name.LESS_THAN; case "Uppercase": return __MODULE__.t_computed_function_name.UPPERCASE; case "Lowercase": return __MODULE__.t_computed_function_name.LOWERCASE; case "length": return __MODULE__.t_computed_function_name.LENGTH; + case "is": + return __MODULE__.t_computed_function_name.IS; case "concat_space": return __MODULE__.t_computed_function_name.CONCAT_SPACE; case "concat_comma": diff --git a/packages/perspective/test/js/computed.js b/packages/perspective/test/js/computed.js index 600c52089e..45a254da36 100644 --- a/packages/perspective/test/js/computed.js +++ b/packages/perspective/test/js/computed.js @@ -757,6 +757,326 @@ module.exports = perspective => { table.delete(); }); + it("Computed column of arity 2, equals, ints", async function() { + const table = perspective + .table({ + a: [100, 75, 50, 25, 10, 1], + b: [100, 100, 100, 100, 100, 100] + }) + .add_computed([ + { + column: "result", + computed_function_name: "==", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([true, false, false, false, false, false]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, equals, floats", async function() { + const table = perspective + .table({ + a: [1.2222222222, 5.5, 7.55555555555, 9.5], + b: [1.22222222222, 5.5, 7.55555555555, 4.5] + }) + .add_computed([ + { + column: "result", + computed_function_name: "==", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, true, true, false]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, equals, mixed", async function() { + const table = perspective + .table({ + a: [100.0, 65.5, 100.0, 85.5, 95.5], + b: [100, 100, 100, 100, 100] + }) + .add_computed([ + { + column: "result", + computed_function_name: "==", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([true, false, true, false, false]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, equals, with null", async function() { + const table = perspective + .table({ + a: [100, null, 50.0, 25, 10, 1], + b: [100, undefined, 50, 100, undefined, 100] + }) + .add_computed([ + { + column: "result", + computed_function_name: "==", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([true, null, true, false, null, false]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, not equals, ints", async function() { + const table = perspective + .table({ + a: [100, 75, 50, 25, 10, 1], + b: [100, 100, 100, 100, 100, 100] + }) + .add_computed([ + { + column: "result", + computed_function_name: "!=", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, true, true, true, true, true]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, not equals, floats", async function() { + const table = perspective + .table({ + a: [1.2222222222, 5.5, 7.55555555555, 9.5], + b: [1.22222222222, 5.5, 7.55555555555, 4.5] + }) + .add_computed([ + { + column: "result", + computed_function_name: "!=", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([true, false, false, true]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, not equals, mixed", async function() { + const table = perspective + .table({ + a: [100.0, 65.5, 100.0, 85.5, 95.5], + b: [100, 100, 100, 100, 100] + }) + .add_computed([ + { + column: "result", + computed_function_name: "!=", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, true, false, true, true]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, not equals, with null", async function() { + const table = perspective + .table({ + a: [100, null, 50.0, 25, 10, 1], + b: [100, undefined, 50, 100, undefined, 100] + }) + .add_computed([ + { + column: "result", + computed_function_name: "!=", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, null, false, true, null, true]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, greater than, ints", async function() { + const table = perspective + .table({ + a: [100, 75, 50, 25, 10, 1], + b: [100, 100, 100, 100, 100, 0] + }) + .add_computed([ + { + column: "result", + computed_function_name: ">", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, false, false, false, false, true]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, greater than, floats", async function() { + const table = perspective + .table({ + a: [1.22222222223, 5.5, 7.55555555555, 0.555555556], + b: [1.22222222222, 5.5, 7.55555555555, 0.555555555] + }) + .add_computed([ + { + column: "result", + computed_function_name: ">", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([true, false, false, true]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, greater than, mixed", async function() { + const table = perspective + .table({ + a: [100.0, 65.5, 100.0, 85.5, 95.5], + b: [100, 100, 100, 100, 5] + }) + .add_computed([ + { + column: "result", + computed_function_name: ">", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, false, false, false, true]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, greater than, with null", async function() { + const table = perspective + .table({ + a: [100, null, 50.0, 25, 10, 10000], + b: [100, undefined, 50, 100, undefined, 100] + }) + .add_computed([ + { + column: "result", + computed_function_name: ">", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, null, false, false, null, true]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, less than, ints", async function() { + const table = perspective + .table({ + a: [100, 75, 50, 25, 10, 1], + b: [100, 100, 100, 100, 100, 0] + }) + .add_computed([ + { + column: "result", + computed_function_name: "<", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, true, true, true, true, false]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, less than, floats", async function() { + const table = perspective + .table({ + a: [1.2222222222, 5.5, 7.1, 9.5], + b: [1.22222222222, 5.5, 7.55555555555, 4.5] + }) + .add_computed([ + { + column: "result", + computed_function_name: "<", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([true, false, true, false]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, less than, mixed", async function() { + const table = perspective + .table({ + a: [100.0, 65.5, 100.0, 85.5, 95.5], + b: [100, 100, 100, 100, 5] + }) + .add_computed([ + { + column: "result", + computed_function_name: "<", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([false, true, false, true, false]); + view.delete(); + table.delete(); + }); + + it("Computed column of arity 2, less than, with null", async function() { + const table = perspective + .table({ + a: [10, null, 50.0, 25, 10, 10000], + b: [100, undefined, 50, 100, undefined, 100] + }) + .add_computed([ + { + column: "result", + computed_function_name: "<", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result["result"]).toEqual([true, null, false, true, null, false]); + view.delete(); + table.delete(); + }); + it("Computed column of arity 2 with updates on non-dependent columns, construct from schema", async function() { var meta = { w: "float", @@ -1103,6 +1423,68 @@ module.exports = perspective => { view.delete(); table.delete(); }); + + it("is", async function() { + const table = perspective + .table({ + a: ["ABC", "DEF", null, "HIjK", "lMNoP"], + b: ["ABC", undefined, null, "HIjK", "lMNoP"] + }) + .add_computed([ + { + column: "result", + computed_function_name: "is", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result.result).toEqual([true, false, false, true, true]); + view.delete(); + table.delete(); + }); + + it("is, nulls", async function() { + const table = perspective + .table({ + a: ["ABC", "DEF", undefined, null, null], + b: ["ABC", "not", "EfG", "HIjK", null] + }) + .add_computed([ + { + column: "result", + computed_function_name: "is", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result.result).toEqual([true, false, false, false, false]); + view.delete(); + table.delete(); + }); + + it("is, extra long", async function() { + const table = perspective + .table({ + a: ["ABC".repeat(10), "DEF".repeat(10), null, "HIjK".repeat(10), "lMNoP"], + b: ["ABC".repeat(10), "DEF".repeat(10), undefined, "HIjK", "lMNoP"] + }) + .add_computed([ + { + column: "result", + computed_function_name: "is", + inputs: ["a", "b"] + } + ]); + console.log(await table.size()); + let view = table.view(); + let result = await view.to_columns(); + console.log(result); + expect(result.result).toEqual([true, true, false, false, true]); + view.delete(); + table.delete(); + }); }); describe("Date, Arity 1", function() { From 0b34189a346df028e45cd1b08338b7cc63af7e61 Mon Sep 17 00:00:00 2001 From: Jun Tan Date: Tue, 3 Mar 2020 09:29:04 -0500 Subject: [PATCH 2/3] no more allocations --- cpp/perspective/src/cpp/computed_function.cpp | 5 +- packages/perspective/test/js/computed.js | 94 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/cpp/perspective/src/cpp/computed_function.cpp b/cpp/perspective/src/cpp/computed_function.cpp index a5ae0f9858..6499fe115f 100644 --- a/cpp/perspective/src/cpp/computed_function.cpp +++ b/cpp/perspective/src/cpp/computed_function.cpp @@ -429,9 +429,8 @@ t_tscalar is(t_tscalar x, t_tscalar y) { return rval; } - std::string x_str = x.to_string(); - std::string y_str = y.to_string(); - rval.set(x_str == y_str); + bool eq = strcmp(x.get_char_ptr(), y.get_char_ptr()) == 0; + rval.set(eq); return rval; } diff --git a/packages/perspective/test/js/computed.js b/packages/perspective/test/js/computed.js index 45a254da36..00e2d732dd 100644 --- a/packages/perspective/test/js/computed.js +++ b/packages/perspective/test/js/computed.js @@ -1477,14 +1477,104 @@ module.exports = perspective => { inputs: ["a", "b"] } ]); - console.log(await table.size()); let view = table.view(); let result = await view.to_columns(); - console.log(result); expect(result.result).toEqual([true, true, false, false, true]); view.delete(); table.delete(); }); + + it("is, short", async function() { + const table = perspective + .table({ + a: ["A", "E", null, "h", "l"], + b: ["a", "E", undefined, "h", "l"] + }) + .add_computed([ + { + column: "result", + computed_function_name: "is", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result.result).toEqual([false, true, false, true, true]); + view.delete(); + table.delete(); + }); + + it("is, mixed length", async function() { + const table = perspective + .table({ + a: ["ABC".repeat(100), "DEF".repeat(10), null, "hijk".repeat(10), "lm"], + b: ["arc".repeat(50), "DEf".repeat(10), undefined, "HIjK", "lMNoP"] + }) + .add_computed([ + { + column: "result", + computed_function_name: "is", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result.result).toEqual([false, false, false, false, false]); + view.delete(); + table.delete(); + }); + + it("is, UTF-8", async function() { + const table = perspective + .table({ + a: [ + ">ﺐ{׆Meڱ㒕宾ⷭ̽쉱L𞔚Ո拏۴ګPظǭ�PۋV|팺㺞㷾墁鴦򒲹|ۿ򧊊䭪񪩛𬦢񺣠񦋳򵾳蛲񖑐iM񊪝񆷯", + "灙𬡍瀳։󷿙񅈕ǐ-kʂiJ!�P񙺍󵝳̃੝w𬾐򕕉耨󉋦o򰵏詂3򒤹J<ꑭ񃕱Ӏ𛤦4u򉠚UPf􂢳P#�#Q񪂈", + "ĈᔞZ񇌖Qఋ?x?#$12ボլ㕢ﺧ𷛘󽙮[񲸧I񟭝򋨰魏ճ�כ󽺴ۏ󫨫䆐'㓔ǃ[ְ੬䎕寽𤩚ߨ袧򲕊򓰷|%", + "ęԛ򓍯󍩁𨞟㰢œ󇂣õ􌁇΍Ԥ⥯۷˝㿙צּ񬆩򤿭顂ݦۍ式+=�ԋ帋񃴕譋ⴏ0l􅏎߳cί򇈊iȞڈU򆐹񍖮򷡦̥𩮏DZ", + "0ой3֝󻙋򑨮꾪߫0󏜬󆑝w󊭟񑓫򾷄𶳿o󏉃纊ʫ􅋶聍𾋊ô򓨼쀨ˆ퍨׽ȿKOŕ􅽾󙸹Ѩ󶭆j񽪌򸢐p򊘏׷򿣂dgD쩖" + ], + b: [ + ">ﺐ{׆Meڱ㒕宾ⷭ̽쉱L𞔚Ո拏۴ګPظǭ�PۋV|팺㺞㷾墁鴦򒲹|ۿ򧊊䭪񪩛𬦢񺣠񦋳򵾳蛲񖑐iM񊪝񆷯", + "灙𬡍瀳։󷿙񅈕ǐ-kʂiJ!�P񙺍󵝳̃੝w𬾐򕕉耨󉋦o򰵏詂3򒤹J<ꑭ񃕱Ӏ𛤦4u򉠚UPf􂢳P#�#Q񪂈", + "ĈᔞZ񇌖Qఋ?x?#$12ボլ㕢ﺧ𷛘󽙮[񲸧I񟭝򋨰魏ճ�כ󽺴ۏ󫨫䆐'㓔ǃ[ְ੬䎕寽𤩚ߨ袧򲕊򓰷|%", + "ęԛ򓍯󍩁𨞟㰢œ󇂣õ􌁇΍Ԥ⥯۷˝㿙צּ񬆩򤿭顂ݦۍ式+=�ԋ帋񃴕譋ⴏ0l􅏎߳cί򇈊iȞڈU򆐹񍖮򷡦̥𩮏DZ", + "0ой3֝󻙋򑨮꾪߫0󏜬󆑝w󊭟񑓫򾷄𶳿o󏉃纊ʫ􅋶聍𾋊ô򓨼쀨ˆ퍨׽ȿKOŕ􅽾󙸹Ѩ󶭆j񽪌򸢐p򊘏׷򿣂dgD쩖2" + ] + }) + .add_computed([ + { + column: "result", + computed_function_name: "is", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result.result).toEqual([true, true, true, true, false]); + view.delete(); + table.delete(); + }); + + it("is, UTF-8 converted to Unicode", async function() { + const table = perspective + .table({ + a: [">{MeLPPV||iM", "-kiJ!Pwo3J<4uUPfP##Q", "ZQ?x?#$12[I'[|%", "ܦf+=0lciU", "030wo􎼨KOjpdD"], + b: [">{MeLPPV||iM", "-kiJ!Pwo3J<4uUPfP##Q", "ZQ?x?#$12[I'[|%", "ܦf+=0lciU", "030wo􎼨KOjpdD2"] + }) + .add_computed([ + { + column: "result", + computed_function_name: "is", + inputs: ["a", "b"] + } + ]); + let view = table.view(); + let result = await view.to_columns(); + expect(result.result).toEqual([true, true, true, true, false]); + view.delete(); + table.delete(); + }); }); describe("Date, Arity 1", function() { From 558b192f5fa59555c070456094de48da7f6899a4 Mon Sep 17 00:00:00 2001 From: Jun Tan Date: Tue, 3 Mar 2020 19:14:54 -0500 Subject: [PATCH 3/3] give PSP_VERBOSE_ASSERT better error messages --- cpp/perspective/src/include/perspective/base.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cpp/perspective/src/include/perspective/base.h b/cpp/perspective/src/include/perspective/base.h index 0e71bf0b9d..ec3254c5a7 100644 --- a/cpp/perspective/src/include/perspective/base.h +++ b/cpp/perspective/src/include/perspective/base.h @@ -135,13 +135,19 @@ std::is_pod::value && std::is_standard_layout::value , \ #else #define PSP_VERBOSE_ASSERT1(COND, MSG) \ { \ - if (!(COND)) \ - psp_abort("Assertion failed!"); \ + if (!(COND)) { \ + std::stringstream ss; \ + ss << MSG; \ + psp_abort(ss.str()); \ + } \ } #define PSP_VERBOSE_ASSERT2(EXPR, COND, MSG) \ { \ - if (!(EXPR COND)) \ - psp_abort("Assertion failed!"); \ + if (!(EXPR COND)) { \ + std::stringstream ss; \ + ss << MSG; \ + psp_abort(ss.str()); \ + } \ } #define PSP_COMPLAIN_AND_ABORT(X) psp_abort(X); #define PSP_ASSERT_SIMPLE_TYPE(X)