Difference between t.foo and t[foo] in Lua - indexing

Given a table
t = {foo = "bar", bar = "foo"}
and a variable
foo = "bar"
what's the difference between
print(t.foo)
which prints "bar" and
print(t[foo])
which prints "foo"

t[expr] is the indexing operation, where t is the table to be indexed and expr is the expression of which the value is used as key. t[foo] thus evaluates to t["bar"]. The value for the key bar is the string foo. Thus print(t[foo]) prints "foo".
t.name is merely a shorthand for t["name"] where name matches Lua's lexical convention for an identifier:
Names (also called identifiers) in Lua can be any string of Latin letters, Arabic-Indic digits, and underscores, not beginning with a digit and not being a reserved word. Identifiers are used to name variables, table fields, and labels.
- Lua 5.4 Reference Manual
This means that name is not evaluated as the expression name but rather as the string literal "name" when indexing the table. Thus t.foo is equivalent to t["foo"] which evaluates to bar.
TL;DR: To index tables with the variables of values or other expressions, use t[expr]. In particular you must use t[index] to index the list part of tables. You also have to use t[expr] if expr is a string literal that does not qualify as an identifier (ex: t["foo-bar"]). To index tables with keys that are names / identifiers, use t.name.

Related

How ''~'' and ''^'' actually works with practical examples in PostgreSQL?

I'm trying to solve a case that, a lot of users have used the syntax that contains the "~".
As below:
select
business_postal_code as zip,
count(distinct case when left(business_address,1) ~ '^[0-9]' then lower(split_part(business_address, ' ', 2))
else lower(split_part(business_address, ' ', 1)) end ) as n_street
from sf_restaurant_health_violations
where business_postal_code is not null
group by 1
order by 2 desc, 1 asc;
link to acess the case: https://platform.stratascratch.com/coding/10182-number-of-streets-per-zip-code?python=
But I couldn't undernstand how this part of the code actually works: ... ~ '^ ....
Let's simplify the query in your question to the component parts you're asking about. Once we see how they work individually, perhaps the whole query will make more sense.
To start, the ~ (tilde) is the POSIX, case-sensitive regular expression operator. The linked PostgreSQL documentation provides brief descriptions and usage examples of it and its sibling operators:
Operator
Description
Example
~
Matches regular expression, case sensitive
'thomas' ~ '.*thomas.*'
~*
Matches regular expression, case insensitive
'thomas' ~* '.*Thomas.*'
!~
Does not match regular expression, case sensitive
'thomas' !~ '.*Thomas.*'
!~*
Does not match regular expression, case insensitive
'thomas' !~* '.*vadim.*'
We can see that each operator has two operands: a constant string on the left, and a pattern on the right. If the string on the left is a match for the pattern on the right, the statement is true, otherwise it is false.
In the given example for the operator you're asking about, 'thomas' is a match for the pattern '.*thomas.*' by standard regular expression rules. The '.*' pre-and-postfixes mean "match any character (except newline) any number of times (zero or more)". The whole pattern then means, "match any character any number of times, then the literal string 'thomas', then any character any number of times". One such match would be 'john thomas jones' where 'john ' matches the first '.*' and ' jones' matches the second '.*'.
I don't think this is a great example because it is functionally equivalent to 'thomas' LIKE '%thomas%' which is likely to run faster, among other benefits like being a SQL-standard operator.
A better example is the query in your question where the pattern '^[0-9]' is used. Setting aside the ^ for now, this pattern means, "match any character in 0-9 (0, 1, 2, ..., 8, 9)", which would be much more verbose if you were to use the LIKE operator: field LIKE '^0' OR field LIKE '^1' OR field LIKE '^2' ....
The ^ operator is not PostgreSQL-specific. Rather it is a special character in regular expressions with one of two meanings (aside from its use as a literal character; more about that in this answer):
The match should begin at the start of the line/string.
For example, the string "Hello, World!" would contain a match for the pattern 'World' since the word "World" appears in it, but would not contain a match for the pattern '^World' since the word "World" is not at the start of the string.
The string "Hello, World!" would contain a match for both of the following patterns: 'Hello' and '^Hello' since the word "Hello" is at the start of the string.
The given character set should be negated when making a match.
For example, the pattern [^0-9] means, "match any character that is not in the range 0-9". So 'a' would match, '&' would match, and 'G' would match, but '7' would not match since it is in the character set that is being excluded.
The query in your question uses the first of the two meanings. The pattern '^[0-9]' means, "match any character in the range 0-9 starting at the beginning of the string". So '0123' would match since the string starts with "0", but 'a5' would not match since the string starts with "a" which is not the character set that is being matched.
Back to the query in your question, then. The relevant part reads:
1 count(distinct
2 case
3 when left(business_address, 1) ~ '^[0-9]'
4 then lower(split_part(business_address, ' ', 2))
5 else lower(split_part(business_address, ' ', 1))
6 end
7 ) as n_street
Line 3 contains a regular expression match that will determine if we should use this case in the overall CASE statement. If the string matches the pattern, the expression will be true and we will use this case. If the string does not match the pattern, the expression will be false and we will try the next case.
The string we are matching to the pattern is left(business_address, 1). The LEFT function takes the first n characters from the string. Since n is "1" here, this returns the first character of the field business_address.
The pattern we are trying to match this string to is '^[0-9]' which we have already said means, "match any character in the range 0-9 starting at the beginning of the string". Technically we don't need the ^ regex operator here since LEFT(..., 1) will return at most one character (which will always be the first character in the resulting string).
As an example, if business_address is "123 Jones Street, Anytown, USA", then LEFT(business_address, 1) will return "1" which will match the pattern (and therefore the expression will be true and we will use the first case).
If, instead, business_address were "Jones Plaza, Suite 123, Anytown, USA", then LEFT(business_address, 1) would return "J" which would not match the pattern (since the first character is "J" which is not in the range 0-9). Our expression would be false and we would continue to the next case.

While calling the loadquery why we pass "%"

I have a id,title, discription saved in my sqlite database, so while loading i pass the loadquery("%") i am not understaning what this % means, i am providing you the function code.
// calling the loadquery
Loadquery("%")
fun Loadquery(title:String)
{
var dbManager=DbManager(this)
val projections= arrayOf("ID","Title","Description")// array of cols
val selectionArgs= arrayOf(title) // array of rows
val cursor=dbManager.Query(projections,"Title like ?",selectionArgs,"Title")
list_notes.clear()
if(cursor.moveToFirst())
{
do{
val ID=cursor.getInt(cursor.getColumnIndex("ID"))
val Title=cursor.getString(cursor.getColumnIndex("Title"))
val Description=cursor.getString(cursor.getColumnIndex("Description"))
list_notes.add(note(ID,Title,Description))
}
while (cursor.moveToNext())
}
var myNotesAdapter= myadapter(list_notes)
lis.adapter=myNotesAdapter
}
The % is a wild sequence, so the query will return all rows where the Title is anything.
If you passed a% then it would return all rows that start with a (or A as LIKE is case insensitive).
If you passed %a% then it would return all rows that have an a anywhere in the in the title and so on.
SQL As Understood By SQLite - expression - The LIKE, GLOB, REGEXP, and MATCH operators says :-
The LIKE operator does a pattern matching comparison.
The operand to
the right of the LIKE operator contains the pattern and the left hand
operand contains the string to match against the pattern.
A percent
symbol ("%") in the LIKE pattern matches any sequence of zero or more
characters in the string.
An underscore ("_") in the LIKE pattern
matches any single character in the string. Any other character
matches itself or its lower/upper case equivalent (i.e.
case-insensitive matching).
Important Note: SQLite only understands
upper/lower case for ASCII characters by default. The LIKE operator is
case sensitive by default for unicode characters that are beyond the
ASCII range.

Can I use Regular Expressions in USQL?

Is it possible to write regular expression comparisons in USQL?
For example, rather than multiple "LIKE" statements to search for the name of various food items, I want to perform a comparison of multiple items using a single Regex expression.
You can create a new Regex object inline and then use the IsMatch() method.
The example below returns "Y" if the Offer_Desc column contains the word "bacon", "croissant", or "panini".
#output =
SELECT
, CSHARP(new Regex("\\b(BACON|CROISSANT|PANINI)S?\\b"
)).IsMatch(wrk.Offer_Desc.ToUpper())
? "Y"
: "N" AS Is_Food
FROM ... AS wrk
Notes:
The CSHARP() block is optional, but you do need to escape any backslashes in your regex by doubling them (as in the example above).
The regex sample accepts these as a single words, either in singular or plural form ("paninis" is okay but "baconator" is not).
I'd assume it would be the same inline, but when I used regex in code behind I hit some show-stopping speed issues.
If you are checking a reasonable number of food items I'd really recommend just using an inline ternary statement to get the results you're looking for.
#output =
SELECT
wrk.Offer_Desc.ToLowerInvariant() == "bacon" ||
wrk.Offer_Desc.ToLowerInvariant() == "croissant" ||
wrk.Offer_Desc.ToLowerInvariant() == "panini" ? "Y" : "N" AS Is_Food
FROM ... AS wrk
If you do need to check if a string contains a string, the string Contains method might still be a better approach.
#output =
SELECT
wrk.Offer_Desc.ToLowerInvariant().Contains("bacon") ||
wrk.Offer_Desc.ToLowerInvariant().Contains("croissant") ||
wrk.Offer_Desc.ToLowerInvariant().Contains("panini") ? "Y" : "N" AS Is_Food
FROM ... AS wrk

regexp after a word appear

Im using regexp to find the text after a word appear.
Fiddle demo
The problem is some address use different abreviations for big house: Some have space some have dot
Quinta
QTA
Qta.
I want all the text after any of those appear. Ignoring Case.
I try this one but not sure how include multiple start
SELECT
REGEXP_SUBSTR ("Address", '[^QUINTA]+') "REGEXPR_SUBSTR"
FROM Address;
Solution:
I believe this will match the abbreviations you want:
SELECT
REGEXP_REPLACE("Address", '^.*Q(UIN)?TA\.? *|^.*', '', 1, 1, 'i')
"REGEXPR_SUBSTR"
FROM Address;
Demo in SQL fiddle
Explanation:
It tries to match everything from the begging of the string:
until it finds Q + UIN (optional) + TA + . (optional) + any number of spaces.
if it doesn't find it, then it matches the whole string with ^.*.
Since I'm using REGEXP_REPLACE, it replaces the match with an empty string, thus removing all characters until "QTA", any of its alternations, or the whole string.
Notice the last parameter passed to REGEXP_REPLACE: 'i'. That is a flag that sets a case-insensitive match (flags described here).
The part you were interested in making optional uses a ( pattern ) that is a group with the ? quantifier (which makes it optional). Therefore, Q(UIN)?TA matches either "QUINTA" or "QTA".
Alternatively, in the scope of your question, if you wanted different options, you need to use alternation with a |. For example (pattern1|pattern2|etc) matches any one of the 3 options. Also, the regex (QUINTA|QTA) matches exactly the same as Q(UIN)?TA
What was wrong with your pattern:
The construct you were trying ([^QUINTA]+) uses a character class, and it matches any character except Q, U, I, N, T or A, repeated 1 or more times. But it's applied to characters, not words. For example, [^QUINTA]+ matches the string "BCDEFGHJKLMOPRSVWXYZ" completely, and it fails to match "TIA".

Impossible to match a digit with a REGEXP_REPLACE

I try to extract the '930' from 'EM 930' with following Regexp
REGEXP_REPLACE(info,'^[:space:]*[ABCDEFGHIJKLMNOPQRSTUVWXYZ]*[:space:]*([0-9]+)[:space:]*$','\1')
But it returns me the original string.
An idea why ?
Subsidiary question:
Why does the "\1" returned the original string when the pattern is not matched ? I expected it to return NULL, as in my other regexp experiences (eg Perl).
Who I can re-write this in a performant way so that I get of wel the matched string of well NULL ?
Your space character class was not exactly correct. If we change [:space:] to [[:space:]], your regexp_replace works as you expect:
REGEXP_REPLACE(info, '^[[:space:]]*[ABCDEFGHIJKLMNOPQRSTUVWXYZ]*[[:space:]]*([0-9]+)[[:space:]]*$','\1')
For the sake of succinctness, we could use the upper character class, [[:upper:]], for [ABCDEFGHIJKLMNOPQRSTUVWXYZ]. This changes the function invocation to:
regexp_replace(info, '^[[:space:]]*[[:upper:]]*[[:space:]]*([0-9]+)[[:space:]]*$','\1')
Or escape characters could be used in lieu of character classes:
\s space
\w word character
\d digit character
regexp_replace(info, '^\s*\w*\s*(\d+)\s*$','\1')
Explanation:
Since your malformed character class, [:space:], does not match the space that exists between 'EM' and '930', your search by parameter does not match any characters in the source parameter.
Your search by parameter, '^[[:space:]]*[ABCDEFGHIJKLMNOPQRSTUVWXYZ]*[[:space:]]*([0-9]+)[[:space:]]*$', is anchored to the beginning and end of the column, info, thus it can only match the column, info, one time at most.
In your case, there is no match and the character group, '\1' which is associated with '([0-9]*)', has no value.
Consequently, no characters are replaced and you are left with original value of the column, info, 'EM 930'.
interesting variations to better understand this function:
-If your corrected function invocation had no pattern_to_replace_by parameter, '\1', then a NULL would be returned:
regexp_replace(info, '^\s*\w*\s*(\d+)\s*$' ) FROM dual;
-Since you have a pattern_to_replace_by parameter, '\1', and now it has the matching character group, the repeating digit group is returned:
930