Better way to program in matrix style selections in plsql? - if-statement

By matrix style, I mean having n variables, each with some number of inputs, and having to handle all possible values. The simplest case of this is multiple boolean values and having to handle every combination of true/false. This is easy if the returned values follow certain patterns, but otherwise it seems quite difficult.
(If there is a better name than 'matrix style', please comment and tell me so I can update the title.)
The ugly way to handle this is an if else chain.
IF self.A = 'N' THEN
IF self.B = 'N' THEN
...
ELSE
...
END IF;
ELSE
IF self.B = 'N' THEN
...
ELSE
...
END IF;
END IF;
Good luck keeping track of that mess, especially with more than 4 variables.
A slightly more readable way of doing this is to do all the checks together.
IF self.A = 'N' AND ... self.Y = 'N' AND self.Z = 'N' THEN
returnValue := 'Bob';
END IF;
If self.A = 'N' AND ... self.Y = 'N' AND self.Z = 'Y' THEN
returnValue := 'Birthday Party';
END IF;
...
If self.A = 'Y' AND ... self.Y = 'N' AND self.Z = 'N' THEN
returnValue := 'What did I ever do to deserve this?';
END IF;
...
If self.A = 'Y' AND ... self.Y = 'Y' AND self.Z = 'Y' THEN
returnValue := 'Thank God I am done!';
END IF;
You can make that a little better if you do a CASE statement instead of a bunch of if/elses, but that is still very hard to maintain. Imagine accidentally putting a Y instead of an N some place and having to go find it. Considering the chance for errors grows exponentially with each new variable added (as you at least double the amount of code you need to write), there is a good chance for errors in any significant sized problem like this.
You can potentially do some interesting text replacement to try to reduce errors. I recently did this with 5 variables. I started with...
NNNNN
NNNNY
...
YYYYN
YYYYY
Then I ran some find and replace over them using Notepad++ to try to reduce the chance of mistyping a N or Y. But the end product still looks nasty to maintain. So I'm wondering if there are any better ways to handle this (mostly in terms of maintainability, though any efficiency boost without loosing maintainability are also welcome suggestions). While I'm looking specifically for PL/SQL solutions, any solutions in other languages are still welcome because they might be able to be translated to PL/SQL.
Edit: In case anyone is trying to solve this problem and wants to use my current solution, here is the find and replace.
Find: ([Y,N]) repeated as many times as you have variables.
Replace: \t\t\tWHEN self.valueName = '\1' THEN\r\n\t\t\t\treturnValue := '' where the self.valueName = '\1' is repeated once for each variable you have, with the \1 incremented each time. You'll also need to set the correct number of \t's so that it matches however much indented it should be. This works in Notepad++, regex mode.

Why do you have that problem? I assume that this is a variable of a type consisting out of variables from A-Z. So how do you populate this in the first place? Can't you simplify right there?
But if there is no alternative you can first check if there is only 'Y' and 'N' in the single fields and convert to 1 and 0 and make numbers out of it and check against the numbers. E.g. NNNNY becomes one and NNNYN becomes 2 etc. Then it is IF r=1 then .. elsif r=2 ..
A probably even better alternative is to generate the code. You can form a string that has the "create or replace functionX as ..." and do an execute immediate on it.

Try to concatenate to one value (self.A || self.B || .. || self.Z) and then use case on values like 'NNNNN', 'NNNNY', 'NNNYN, etc.
EDIT:
I made assumtions that:
you have large set of one-char variables that...
...you want to translate to single return value...
...covering all-or-many possibile sets.
What you gain is a view of all combinations, one per line, one key under other, in each single-char column having same self.variable. If number of variables is really big, to avoid options like YNNYYYNYNNYNYYYNNY you can improve readability to put space every 3-rd or 5-th or n-th char:
when 'NNN NNN NNN' then '1st choice'
when 'NNN NNN NNY' then '2nd choice'
when 'NNN NNN NYN' then '3rd choice'
... ... ...
As #hol in answer below suggested generating code is a very good choice in that case.
But you, as developer, should know if m-th choice is YYY YNY YY or YYY NYY YY, no other way to be sure whether you get correct results than to check/test code.

Related

Universe OCONV argument for zero-padding

I'm looking for some argument (ARG) such that this code:
A = 5
B = OCONV(A,'ARG5')
PRINT B
will print to the screen
00005
Anybody know something which will do this for me?
In Universe I would use the MR% conversion code. Just be aware that it will truncate anything longer than 5 characters.
A = 5
B = OCONV(A,'MR%5')
PRINT B
I use this a lot when I need to use EVAL in a conditional or as an aggregate function in a SQL or other TCL statement like to find the record with the most fields in a file.
SELECT MAX(EVAL "DCOUNT(#RECORD,#FM)") FROM VOC;
SELECT MAX(EVAL "OCONV(DCOUNT(#RECORD,#FM),'MR%8')") FROM VOC;
Masking aside these generally return 2 different values on our system.
I am using UniData, but looking at the commands reference manual I can't see anything quite right, in terms of one simple argument to OCONV, or similar. I came up with these (somewhat kludgy) alternatives, though:
NUMLEN=5
VALUE=5
PRINT CHANGE(SPACES(NUMLEN-LEN(VALUE))," ","0"):VALUE
Here you are using the SPACES function to create that amount of space characters and then convert them to zeros.
PRINT OCONV(VALUE,"MR":NUMLEN:"(#####)")
This is using OCONV but has to define a string with the "mask" to only shew the final 5 digits. So if NUMLEN changes then the mask string definition would have to change.
PRINT OCONV(VALUE,"MR":NUMLEN)[3,NUMLEN]
This version uses OCONV but prints starting at the 3rd character and shews the next NUMLEN characters, therefore trimming off the initial "0." that is made by using the "MR" parameter
PADDED.VALUE = VALUE 'R%5' is the simplest way to do this.

Using regex for dividing a substrings by comma based on repeating groups

This is a coding exercise. I'm supposed parse html to a string using python such that a string of html like the following:
"<div><p><b></b></p><p></p><p></p></div>"
Becomes:
"DIV([P([B([])]),P([]),P([])])"
Where each global tag that encloses other ones have to be returned separated by a comma.
I understand that regex is not the best choice for this kind of job. Nevertheless, I have a limited set of tools available of which regex is one of them.
So far, what I have is the following:
repl_from = ["<div>", "</div>", "<img />", "<p>", "</p>", "<b>", "</b>"]
for i in repl_from:
if i == "<div>":
j = "DIV(["
elif i == "<img />":
j = "IMG({})"
elif i == "<p>":
j = "P(["
elif i == "<b>":
j = "B(["
else: j = "])"
html = html.replace(i, j)
This gets me DIV([P([B([])])P([])P([])]). Now I have to divide the inner arguments by commas, and this where I thought about using regexes. But I'm lost in this regards.
I have a seudo code that goes something like this:
1) Find the opening of a global tag (patternI = '[A-Z]+\(\[)')
2) Check if what follows are repeating tags (patternII = '[A-Z]+\(\[\]\)+')
3)If so, get start and end index of patternII, and then just do a replace with the commas. This last part can be simply executed by splitting using the split() and later the join()function, I think.
How can I implement the last part of the algorithm?
EDIT
Ok, I think I made a mistake when explaining the situation. For any tag that encloses another set of tags (like <div><p></p><p></p></div>) the enclosed tags must be parsed as arguments to the global one (therefore: DIV([P([]), P([])])); if the global tag encloses only one, then there are no commas added (<div><p></p></div> will turn out to be DIV([P([])]). In the case when there's no enclosed tag (like so <p></p><b></b>) then when they are transformed, then they carry no commas in between (as such P([])B([])).
I am sure I am not understanding something here but if this is the case why not just use a simple:
a="DIV([P([B([])])P([])P([])])"
import re
print(re.sub(r"\)[A-Z]","),P",a))
This will give:
'DIV([P([B([])]),P([]),P([])])'
I must apologise for the fact that I don't know html at all so I can only try to match what you come up with as value of "a"(since I can't imagine all the possible cases that may exist) in regards to your recent comment:
lest say:
a="DIV([P([B([])])P([])B([])])P([])B([])"
This we can fix with a mix of re.findall and re.sub:
first we will find all that we want to replace in a:
b=re.findall(r"\)[A-Z]",a)
print(b)
this will give:
[')P', ')B', ')P', ')B']
after that we will need to insert a comma in belween each element as we will use this to substitute the original elements:
for i in range(len(b)):
b[i]=b[i][0]+","+b[i][1]
print(b)
this will give:
['),P', '),B', '),P', '),B']
then we will use this b to substitute :
for i in range(len(b)):
a=re.sub(r"\)[A-Z]",b[i],a,1)
print(a)
which will give:
DIV([P([B([])]),P([]),B([])]),P([]),B([])
so the entire above code will look like:
import re
a="DIV([P([B([])])P([])B([])])P([])B([])"
b=re.findall(r"\)[A-Z]",a)
for i in range(len(b)):
b[i]=b[i][0]+","+b[i][1]
for i in range(len(b)):
a=re.sub(r"\)[A-Z]",b[i],a,1)
print(a)
P.S.: please just share the possible values of a for which it won't work and the final result you expect from that. I will be able to match it for that.

PythonQuestion on Longest Common Substring(LCS) algorithm

I'm pretty new to Python, it's my first programming language, and I've wanted to work on some manual data structure manipulation and playing around.
I've recently been learning the basic algorithm for solving the LCS problem, and I understand how it works besides one line of code that I for some weird reason can't seem to convince myself I am grasping entirely.
this is the code I've been using to learn from after I couldn't get it down myself quite right.
EDIT 2: Anyway to make this work with an input of two lists of integers?**I figured out that I was understanding my original question correctly, but would anyone know how I could make this work with a **list of integers? I tried converting S and T to a string of comma separated values, which worked in matching some of the characters, but even then it rarely worked in most test-cases. I'm not sure why it wouldn't, as it is still just two strings being compared, but with commas.
def lcs(S,T):
m = len(S)
n = len(T)
counter = [[0]*(n+1) for x in range(m+1)]
longest = 0
lcs_set = set()
for i in range(m):
for j in range(n):
if S[i] == T[j]:
c = counter[i][j] + 1
counter[i+1][j+1] = c
if c > longest:
lcs_set = set()
longest = c
lcs_set.add(S[i-c+1:i+1])
elif c == longest:
lcs_set.add(S[i-c+1:i+1])
return lcs_set
Now my issue is understanding is the line : lcs_set.add(S[i-c+1:i-1])
I understand that the counter is incremented when a match is found, to give longest the length of the substring. So, to make it easy, if S = Crow and T = Crown, when you reach w, the last match, the counter is incremented to 4, and i is at index 3 of S.
Does this mean I am to read this as: i (index3 on S, the W) - c (4), so 3-4 = -1, so 3-4+1 = 0 (at C) and for the right side of the slice: i(3) + 1 = 4(N, but will not be included, obviously), meaning we end with S[0:4], Crow, to LCS_Set?
If that is the case, I guess I am confused as to why we are adding the whole substring to the set, and not just the newest matched character?
If I understand right, it is updating LCS_set with the entire slice of the current matched substring, so if it were on the second match, R, the counter would be at 2, i would be at 1, and it would be saying S[1-2+1:i(1)+1], so 1-2 = -1, -1 + 1 = 0(C) up to i(1)+1 = 2 (leaving us with S[0:2], or CR), so each time around, the set is updated with the entire substring, and not just the current index.
It's not really a problem, I just want to make sure I'm understanding this correctly.
I would really appreciate any input, or any tips anyone might see with my current logic!!
EDIT:
I just realized I was totally forgetting that the position at C is the current counter number, therefore it obviously wouldn't be updating the LCS_set with the current max match number, and it can't update it with just the current matched letter, so it has to take the slice of the substring in order to update the LCS_Set.
Thanks in advance!

Using If and Else statements in Ruby

Very new to the world of programming and just starting to learn, working through Flatiron School prework and have been doing ok but unable to understand "if" and "else" statements for some reason. The problem is similiar to Chris Pine 'deaf grandma' problem but without saying "BYE!" three times.
~The method should take in a string argument containing a phrase and check to see if the phrase is written in all uppercase: if it isn't, then grandma can't hear you. She should then respond with (return) HUH?! SPEAK UP, SONNY!.
~However, if you shout at her (i.e. call the method with a string argument containing a phrase that is all uppercase, then she can hear you (or at least she thinks that she can) and should respond with (return) NO, NOT SINCE 1938!
I have so far:
def speak_to_grandma
puts "Hi Nana, how are you?".upcase
if false
puts "HUH?! SPEAK UP, SONNY!"
else
puts "NO, NOT SINCE 1938!"
end
end
but am getting wrong number of arguments...how am I supposed to add argument while using the if/else statements? This is probably a very easy and basic question but can't seem to get my head around this (overthinking probably).
Any help and clarity would be greatly appreciated.
input_phrase = "Hi Nana, how are you?"
def speak_to_grandma(phrase)
# Check if string equals same phrase all upper case letters, which means string is all uppercase
if phrase == phrase.upcase
# return this string if condition is true
puts "NO, NOT SINCE 1938!"
else
# return this string if condition is false
puts "HUH?! SPEAK UP, SONNY!"
end
end
# execute function passing input_phrase variable as argument
speak_to_grandma(input_phrase)
how am I supposed to add argument while using the if/else statements?
This is probably a very easy and basic question but can't seem to get
my head around this (overthinking probably).
Your mistake was that function was not accepting any arguments, here it accepts "phrase" variable as argument and processes it:
def speak_to_grandma(phrase)
You had
if false
but did not check what exactly is false.. To rewrite my version with "false" :
input_phrase = "Hi Nana, how are you?"
def speak_to_grandma(phrase)
# Check if it is false that string is all upper case
if (phrase == phrase.upcase) == false
# return this string if condition is false
puts "HUH?! SPEAK UP, SONNY!"
else
# return this string if condition is true
puts "NO, NOT SINCE 1938!"
end
end
speak_to_grandma(input_phrase)
Here I am evaluating
if (phrase == phrase.upcase) == false
Basically means "if expression that phrase equals phrase all uppercase is false"

Regular expressions for phone number patterns

Do you know if is possible to transform a pattern like this to regular expressions:
ABCDXXXXYYYY
Where ABCDEFGH.. are consecutive numbers and V, X, Y, Z are any number.
The pattern above should match:
123400006666
456799994444
etc.
Please note that I'm not asking for a full solution, but some idea on how to approach this problem. Have you ever faced a situation like this before (to search a DB for defined patterns that doesn't seem to fit RegExps?
Any comment would be really appreciated.
You can't identify consecutive numbers in a regular expression as they're too context dependant.
However, I think this would be easily possible in PL/SQL and possibly possible in SQL.
If you only want to use SQL then you can generate a string of consecutive numbers using a combination of connect by and either the undocumented function wm_contact or the user-defined function stragg
Something like:
select replace(stragg(level),',','')
from dual
connect by level <= 5
Concatenating this with a regular expression may get you close but I don't think that this is the way to go. I would definitely investigate using a PL/SQL function and possibly forgetting about regular expressions completely.
Doing the following will split out a number into an array, which you can then loop through and manipulate. As requested, this is just a starting point and you may want to change it around a lot. As there's no actual SQL and it's just string manipulation it's pretty efficient doing something like this.
create or replace function validate_phone( P_phone number )
return number is
type t__phone is table of number index by binary_integer;
t_phone t__phone;
l_consecutive varchar2(150);
begin
-- Test whether we actually have a number first ( code below ).
if is_number(P_phone) = 0 then
return null;
end if;
-- Split out the phone number into individual array elements.
for i in 1 .. length(to_char(P_phone)) loop
t_phone(i) := substr(to_char(P_phone, i, 1))
end loop;
for i in t_phone.first .. t_phone.last loop
-- If we find a consecutive number then build this string.
if t_phone.exists(i + 1)
and t_phone(i) = t_phone(i + 1) - 1 then
l_consecutive := l_consecutive || t_phone(i);
end if;
end loop;
return something;
end validate_phone;
You may, as indicated in the above want to check whether your phone number is actually numeric first:
create or replace function is_number( P_number varchar2 )
return number is
/* Test a number to see whether it actually is one
return a 1 / 0 rather than boolean so it can also
be used in plain SQL.
*/
l_number number;
begin
l_number := P_number;
return 1;
exception when others then
return 0;
end is_number;
The language you describe is not context-free (in case the length of the prefix consisting of consecutive numbers is arbitrary) and not a regular language, therefore can not be expressed by a regular expression.