I am trying to count elements in a table that has some elements indexed with strings. When I try to use the # operator, it just ignores string indexed ones. example:
local myTab = {1,2,3}
print(#myTab)
will return 3
local myTab = {}
myTab["hello"] = 100
print(#myTab)
will return 0
mixing them, I tried
local myTab = {1,2,3,nil,5,nil,7}
print(#myTab)
myTab["test"] = try
print(#myTab)
returned 7 and then 3, that is right because I read somewhere that the # operator stops when it finds a nil value (but then why the first print printed 7?)
last, I tried
local myT = {123,456,789}
myT["test"] = 10
print(#myT)
printing 3, not 4
Why?
The rule is simple, from the length operator:
Unless a __len metamethod is given, the length of a table t is only defined if the table is a sequence, that is, the set of its positive numeric keys is equal to {1..n} for some non-negative integer n. In that case, n is its length.
In your example:
local myTab = {1,2,3,nil,5,nil,7}
#mytab is undefined because myTab isn't a sequence, with or without myTab["test"] = try.
local myT = {123,456,789}
myT is a sequence, and the length is 3, with or without myT["test"] = 10
Related
Goal: write a function in PostgreSQL SQL that takes as input an integer array whose each element is either 0, 1, or -1 and returns an array of the same length, where each element of the output array is the sum of all adjacent nonzero values in the input array having the same or lower index.
Example, this input:
{0,1,1,1,1,0,-1,-1,0}
should produce this result:
{0,1,2,3,4,0,-1,-2,0}
Here is my attempt at such a function:
CREATE FUNCTION runs(input int[], output int[] DEFAULT '{}')
RETURNS int[] AS $$
SELECT
CASE WHEN cardinality(input) = 0 THEN output
ELSE runs(input[2:],
array_append(output, CASE
WHEN input[1] = 0 THEN 0
ELSE output[cardinality(output)] + input[1]
END)
)
END
$$ LANGUAGE SQL;
Which gives unexpected (to me) output:
# select runs('{0,1,1,1,1,0,-1,-1,-1,0}');
runs
----------------------------------------
{0,1,2,3,4,5,6,0,0,0,-1,-2,-3,-4,-5,0}
(1 row)
I'm using PostgreSQL 14.4. While I am ignorant of why there are more elements in the output array than the input, the cardinality() in the recursive call seems to be causing it, as also does using array_length() or array_upper() in the same place.
Question: how can I write a function that gives me the output I want (and why is the function I wrote failing to do that)?
Bonus extra: For context, this input array is coming from array_agg() invoked on a table column and the output will go back into a table using unnest(). I'm converting to/from an array since I see no way to do it directly on the table, in particular because WITH RECURSIVE forbids references to the recursive table in either an outer join or subquery. But if there's a way around using arrays (especially with a lack of tail-recursion optimization) that will answer the general question (But I am still very very curious why I'm seeing the extra elements in the output array).
Everything indicates that you have found a reportable Postgres bug. The function should work properly, and a slight modification unexpectedly changes its behavior. Add SELECT; right after $$ to get the function to run as expected, see Db<>fiddle.
A good alternative to a recursive solution is a simple iterative function. Handling arrays in PL/pgSQL is typically simpler and faster than recursion.
create or replace function loop_function(input int[])
returns int[] language plpgsql as $$
declare
val int;
tot int = 0;
res int[];
begin
foreach val in array input loop
if val = 0 then tot = 0;
else tot := tot + val;
end if;
res := res || tot;
end loop;
return res;
end $$;
Test it in Db<>fiddle.
The OP wrote:
this input array is coming from array_agg() invoked on a table column and the output will go back into a table using unnest().
You can calculate these cumulative sums directly in the table with the help of window functions.
select id, val, sum(val) over w
from (
select
id,
val,
case val
when 0 then 0
else sum((val = 0)::int) over w
end as series
from my_table
window w as (order by id)
) t
window w as (partition by series order by id)
order by id
Test it in Db<>fiddle.
How can I find all possible concatenations of characters in a table [ in a table ] and display them as strings with new lines?
Example, I have a array of tables with characters:
c={
{'1','2','3'},
{'a','b','c'},
{'A','B','C'}
}
I want to find all possible concatenations like
1aA
1aB
1aC
-- second table second character
1bA
1bB
1bC
-- second table third character
1cA
1cB
1cC
-- first table second character
2aA
2aB
2aC
-- and so on...
There could be many tables with however much characters. Appreciate the help!
EDIT:
I've found my answer here,
https://forum.cheatengine.org/viewtopic.php?p=5773931#5773931
This algorithm assumes that there are always an equal number of values in each row, with that assumption we can say you have (#c[1])^(#c) values and use this information to flatten the tables.
c = {
{'1','2','3'},
{'a','b','c'},
{'A','B','C'},
}
local columns = #c[1]
local rows = #c
for i = 0, (columns)^(rows) - 1 do
local output = ""
for k, v in ipairs(c) do
output = output .. v[i % ((columns)^k) // ((columns)^(k - 1)) + 1]
end
print(output)
end
Output:
1aA
2aA
3aA
1bA
2bA
3bA
1cA
2cA
3cA
1aB
2aB
3aB
1bB
2bB
3bB
1cB
2cB
3cB
1aC
2aC
3aC
1bC
2bC
3bC
1cC
2cC
3cC
In Lua, I have a set of tables:
Column01 = {}
Column02 = {}
Column03 = {}
ColumnN = {}
I am trying to access these tables dynamically depending on a value. So, later on in the programme, I am creating a variable like so:
local currentColumn = "Column" .. variable
Where variable is a number 01 to N.
I then try to do something to all elements in my array like so:
for i = 1, #currentColumn do
currentColumn[i] = *do something*
end
But this doesn't work as currentColumn is a string and not the name of the table. How can I convert the string into the name of the table?
If I understand correctly, you're saying that you'd like to access a variable based on its name as a string? I think what you're looking for is the global variable, _G.
Recall that in a table, you can make strings as keys. Think of _G as one giant table where each table or variable you make is just a key for a value.
Column1 = {"A", "B"}
string1 = "Column".."1" --concatenate column and 1. You might switch out the 1 for a variable. If you use a variable, make sure to use tostring, like so:
var = 1
string2 = "Column"..tostring(var) --becomes "Column1"
print(_G[string2]) --prints the location of the table. You can index it like any other table, like so:
print(_G[string2][1]) --prints the 1st item of the table. (A)
So if you wanted to loop through 5 tables called Column1,Column2 etc, you could use a for loop to create the string then access that string.
C1 = {"A"} --I shorted the names to just C for ease of typing this example.
C2 = {"B"}
C3 = {"C"}
C4 = {"D"}
C5 = {"E"}
for i=1, 5 do
local v = "C"..tostring(i)
print(_G[v][1])
end
Output
A
B
C
D
E
Edit: I'm a doofus and I overcomplicated everything. There's a much simpler solution. If you only want to access the columns within a loop instead of accessing individual columns at certain points, the easier solution here for you might just be to put all your columns into a bigger table then index over that.
columns = {{"A", "1"},{"B", "R"}} --each anonymous table is a column. If it has a key attached to it like "column1 = {"A"}" it can't be numerically iterated over.
--You could also insert on the fly.
column3 = {"C"}
table.insert(columns, column3)
for i,v in ipairs(columns) do
print(i, v[1]) --I is the index and v is the table. This will print which column you're on, and get the 1st item in the table.
end
Output:
1 A
2 B
3 C
To future readers: If you want a general solution to getting tables by their name as a string, the first solution with _G is what you want. If you have a situation like the asker, the second solution should be fine.
I am writing a stored procedure that accepts a string as an input and then converts that string to an array with comma as a delimiter, once I have that array I am appending a string '_1' to each element of the array as I need to further utilize that. However when I execute this stored proc to find the result the raise info command behaves differently on printing the value of array (the values will change obviously but the format in which they are displayed changes)
CREATE OR REPLACE FUNCTION Test1(inputlist text) RETURNS text AS $BODY$
DECLARE
a text;
acceptList text[];
counter integer;
length integer;
BEGIN
acceptList = string_to_array(inputList,',');
SELECT array_length(acceptList,1) into length;
RAISE INFO 'Length : %',length;
RAISE INFO 'AcceptList Print 1 : %',acceptList;
counter = 0;
FOREACH a in ARRAY acceptList LOOP
acceptList[counter] = a||'_1';
counter = counter + 1;
END LOOP;
RAISE INFO 'AcceptList Print 2 : %',acceptList;
END;
$BODY$
LANGUAGE plpgsql;
The output in messages tab would be :
INFO: Length : 4
INFO: AcceptList Print 1 : {INC000073535133,INC000073533828,INC000073535942,INC000073535857}
INFO: Acceptlist Print 2 : [0:4]={INC000073535133_1,INC000073533828_1,INC000073535942_1,INC000073535857_1,INC000073535857}
If you notice in the above output the values are appended correctly however, in the print 2 it is showing the size as well as an equal to symbol before printing the values of array
Want to understand why such behavior is seen
By default, arrays in Postgres are indexed from 1, while in the function body the first index of the modified array is 0. As a result, the original array has been extended by one element. The notation [0:4] = {...} means a five-element array with the non-standard first index of 0. Of course, this could be fixed in a simple way:
...
counter = 1;
FOREACH a in ARRAY acceptList LOOP
...
However, note the comment by horse_with_no_name that indicated how this should be done in Postgres with a single query:
select string_agg(concat(t.element, '_1'), ',' order by t.nr)
from unnest(string_to_array('one,two,three',',')) with ordinality as t(element, nr);
Read about arrays in the documentation.
Whenever i am trying to do query with this i am getting words which length is 15 .
select * from 'Table_A' where LENGTH('value') = 5 and value LIKE 'A%S';
LENGTH('value') is always 5. Thus it returns only values that match LIKE 'A%S'
Fix it with:
SELECT * FROM Table_A where LENGTH(value) = 5 and value LIKE 'A%S';
select * from 'Table_A' where LENGTH(value) = 5 and value LIKE 'A%S';
You were checking the length of the literal string 'value', which is always 5. Remove the quotes so you check the length of the data in the column.