Accessing Elements in 2-Dimensional Array Elm - elm

How do I get at certain elements in this array using Array.get?
First I have a 2D List:
node = [['X',' ',' '],['O','O',' '],['X',' ',' ']]
-- so node is a [['X','X',' '],[' ',' ',' '],[' ',' ',' ']] : List (List Char)
I convert it to a 2D array so I get:
Array.fromList [Array.fromList ['X','X',' '],Array.fromList [' ',' ',' '],Array.fromList [' ',' ',' ']] : Array.Array (Array.Array Char)
although side note: why did the repl give me that instead of just reporting it as [['X','X',' '],[' ',' ',' '],[' ',' ',' ']] : Array.Array (Array.Array Char) ? Just wondered, thought that was odd.
so now node's a 2D array instead of a list.
Now what if I want to access the value at say position [0][1] in the 2D array, so the second 'X' in the first row (so that's index 1) 2D array, how would I do that with get?
Once I figure out how to do this then I will need to figure out how to update that position, for example change an X to an O or change an empty position to an X or O
Is it me or just trying to work with 2D Lists or 2D arrays in elm is just a huge PITA?

Is it me or just trying to work with 2D Lists or 2D arrays in elm is just a huge PITA?
Linked list is a more common structure in functional languages and most of the reasoning about algorithms is based on them, that's why working with classic arrays instead can be tedious sometimes (the option is to review the structure of your application and make lists fit into this structure).
Regarding the question, imagine you have such a function:
arrayNode : Array.Array (Array.Array Char)
arrayNode = Array.fromList [Array.fromList ['X','X',' '],Array.fromList [' ',' ',' '],Array.fromList [' ',' ',' ']]
To get a 0 row in this array Array.get function can be used:
Array.get 0 arrayNode
As far as I understand, the difficulty is that Maybe (Array.Array Char) type is returned, so we can't use Array.get one more time straight away.
We could use case expression and check whether the result is Just (Array.Array Char) or Nothing, but actually there's Maybe.andThen function, which can simplify the code:
Array.get 0 arrayNode |> Maybe.andThen (Array.get 1)
The result is Just 'X', as expected

Related

Split strings in a column based on text values and numerical values such as patindex

I have a column that displays stock market options data like below:
GME240119C00020000
QQQ240119C00305000
NFLX240119P00455000
I want to be able to split these up so they show up like:
GME|240119|C|00020000
QQQ|240119|C|00305000
NFLX|240119|P|00455000
I was able to split the first portion with the ticker name by using the code below, but I don't know how to split the rest of the strings.
case patindex('%[0-9]%', str)
when 0 then str
else left(str, patindex('%[0-9]%', str) -1 )
end
from t
edit: for anyone who is wondering, I used Dale's solution below to get my desired outcome. I edited the query he provided to make the parts show up as individual columns
select
substring(T.contractSymbol,1,C1.Position-1) as a
,substring(T.contractSymbol,C1.Position,6) as b
,substring(S1.Part,1,1) as c
,substring(S1.Part,2,len(S1.Part)) as d
from Options_Data_All T
cross apply (
values (patindex('%[0-9]%', T.contractSymbol))
) C1 (Position)
cross apply (
values (substring(contractSymbol, C1.Position+6, len(T.contractSymbol)))
) S1 (Part);
Just keep doing what you started doing by using SUBSTRING. So as you did find the first number and actually in your case, based on the data provided, everything else is fixed length, so you don't have to search anymore, just split the string.
declare #Test table (Contents nvarchar(max));
insert into #Test (Contents)
values
('GME240119C00020000'),
('QQQ240119C00305000'),
('NFLX240119P00455000');
select
substring(T.Contents,1,C1.Position-1) + '|' + substring(T.Contents,C1.Position,6) + '|' + substring(S1.Part,1,1) + '|' + substring(S1.Part,2,len(S1.Part))
from #Test T
cross apply (
values (patindex('%[0-9]%', T.Contents))
) C1 (Position)
cross apply (
values (substring(Contents, C1.Position+6, len(T.Contents)))
) S1 (Part);
Returns:
Data
GME|240119|C|00020000
QQQ|240119|C|00305000
NFLX|240119|P|00455000
If one can assume that all but the first column are fixed width then a simple SUBSTRING solution would suffice e.g.
select
substring(Contents,1,len(Contents)-15)
+ '|' + substring(Contents,len(Contents)-14,6)
+ '|' + substring(Contents,len(Contents)-8,1)
+ '|' + substring(Contents,len(Contents)-7,8) [Data]
from #Test;
Note: CROSS APPLY is just a fancy way to use a sub-query to avoid needing to repeat a calculation.

Outputting results to Pipe delimited format adding extra space

After unsuccessfully using SQL Manager's output options I am hardcoding in pipes to make the output pipe delimited. The problem is that there is a space being added between the pipe and the next column. I've tried LTRIM function and that did not remove the spaces.
SELECT B.BUSINESS_UNIT+'|'
, C.DESCR+'|'
, (CONVERT(CHAR(10),B.INVOICE_DT,121) + '|')
, CAST(B.INVOICE_ID AS varchar) + '|'
, CAST(A.LINE_NBR AS varchar) + '|'
, CAST(B.GROSS_AMT AS varchar) + '|'
, CAST(B.VENDOR_ID AS varchar) + '|'
, D.NAME1 + '|'
, CAST(M.ITM_ID_VNDR AS varchar) + '|'
, J.DESCR + '|'
FROM Some_Table1 B
, SomeTable2 C
, SomeTable3 A
, SomeTable4 D
, SomeTable5 J
, SomeTable6 M
If I copy the results to a text editor there is a space between the pipe and the next column.
It's probably not a space. It's probably a tab.
When you copy a row from the Results pane of SSMS and paste the row into a text editor, or at least into Notepad++, it injects a tab between columns. Turn on Show All Characters or the equivalent to verify.
You can get around that by following Jerry Jeremiah's suggestion in the comments and reducing your output to a single long row:
SELECT CONCAT(B.BUSINESS_UNIT,'|',
C.DESCR,'|',
CONVERT(CHAR(10),B.INVOICE_DT,121),'|',
B.INVOICE_ID,'|'
etc.)
And CONCAT() is your friend here. It takes care of all of the data type conversions for you, except those edge cases like INVOICE_DT where you want a CHAR (although I think an implicit conversion to varchar(x) here would give you the results you're expecting).
Also, as an aside, be cautious casting to varchar without declaring a length. If you do it in the wrong place, it can default to varchar(1), which is seldom what anybody wants. See Bad habits to kick : declaring VARCHAR without (length).

SQL I need to extract a stored procedure name from a string

I am a bit new to this site but I have looked an many possible answers to my question but none of them has answered my need. I have a feeling it's a good challenge. Here it goes.
In one of our tables we list what is used to run a report this can mean that we can have a short EXEC [svr1].[dbo].[stored_procedure] or "...From svr1.dbo.stored_procedure...".
My goal is to get the stored procedure name out of this string (column). I have tried to get the string between '[' and ']' but that breaks when there are no brackets. I have been at this for a few days and just can't seem to find a solution.
Any assistance you can provide is greatly appreciated.
Thank you in advance for entertaining this question.
almostanexpert
Considering the ending character of your sample sentences is space, or your sentences end without trailing ( whether space or any other character other than given samples ), and assuming you have no other dots before samples, the following would be a clean way which uses substring(), len(), charindex() and replace() together :
with t(str) as
(
select '[svr1].[dbo].[stored_procedure]' union all
select 'before svr1.dbo.stored_procedure someting more' union all
select 'abc before svr1.dbo.stored_procedure'
), t2(str) as
(
select replace(replace(str,'[',''),']','') from t
), t3(str) as
(
select substring(str,charindex('.',str)+1,len(str)) from t2
)
select
substring(
str,
charindex('.',str)+1,
case
when charindex(' ',str) > 0 then
charindex(' ',str)
else
len(str)
end - charindex('.',str)
) as "Result String"
from t3;
Result String
----------------
stored_procedure
stored_procedure
stored_procedure
Demo
With the variability of inputs you seem to have we will need to plan for a few scenarios. The below code assumes that there will be exactly two '.' characters before the stored_procedure, and that [stored_procedure] will either end the string or be followed by a space if the string continues.
SELECT TRIM('[' FROM TRIM(']' FROM --Trim brackets from final result if they exist
SUBSTR(column || ' ', --substr(string, start_pos, length), Space added in case proc name is end of str
INSTR(column || ' ', '.', 1, 2)+1, --start_pos: find second '.' and start 1 char after
INSTR(column || ' ', ' ', INSTR(column || ' ', '.', 1, 2), 1)-(INSTR(column || ' ', '.', 1, 2)+1))
-- Len: start after 2nd '.' and go until first space (subtract 2nd '.' index to get "Length")
))FROM TABLE;
Working from the middle out we'll start with using the SUBSTR function and concatenating a space to the end of the original string. This allows us to use a space to find the end of the stored_procedure even if it is the last piece of the string.
Next to find our starting position, we use INSTR to search for the second instance of the '.' and start 1 position after.
For the length argument, we find the index of the first space after that second '.' and then subtract that '.' index.
From here we have either [stored_procedure] or stored_procedure. Running the TRIM functions for each bracket will remove them if they exist, and if not will just return the name of the procedure.
Sample inputs based on above description:
'EXEC [svr1].[dbo].[stored_procedure]'
'EXEC [svr1].[dbo].[stored_procedure] FROM TEST'
'svr1.dbo.stored_procedure'
Note: This code is written for Oracle SQL but can be translated to mySQL using similar functions.

Split String and Mix with another string in redshift

I have two columns one is the description and its corresponding tags in another column is a redshift table
description | Tags
John Plays Football | name, Verb , object
I want the output as description with tag
John name plays verb football object
The one more addition for this is where ever there is a description which contains colon ( : ) I would like to separate the words without removing :
For eg:
description | Tags
Des:John Plays Football | constant,name, Verb, object
Output
Des: constant John name plays verb football object
also need exclude : rule for numbers on both sides of colon to make sure time is getting (eg: 10:10) separated
I have no clue where I will start only
You have create UDF in Python to do this .
Sample code
create function f_py_conc (a srting, b srting)
returns srting
stable
as $$
c=a.split(" ")
d=b.split(",")
e =""
for i in range(len(c))
d=c[i]+" " +d[i]
return e
$$ language plpythonu;
Note: Python is dependent on space so may be this code can not run as it may need some change..
you can actually achieve this using Redshift functions, specifically the split part method. I am not sure if this will address the : as well but give this a go and then comment back with what you need to do next.
The idea is building a concatenated field assembling all the distinct parts of both fields. You can do this as follows:
select description, tags,
SPLIT_PART(description, ' ', 1) + ' ' + SPLIT_PART(tags, ',', 1) + ' ' +
SPLIT_PART(description, ' ', 2) + ' ' + SPLIT_PART(tags, ',', 2) + ' ' +
SPLIT_PART(description, ' ', 3) + ' ' + SPLIT_PART(tags, ',', 3)
as description_with_tag
from table_name
Find out more about this function here

Using Upper to Capitalize the first letter of City name

I am doing some data clean-up and need to Capitalize the first letter of City names. How do I capitalize the second word in a City Like Terra Bella.
SELECT UPPER(LEFT([MAIL CITY],1))+
LOWER(SUBSTRING([MAIL CITY],2,LEN([MAILCITY])))
FROM masterfeelisting
My results is this 'Terra bella' and I need 'Terra Bella'. Thanks in advance.
Ok, I know I answered this before, but it bugged me that we couldn't write something efficient to handle an unknown amount of 'text segments'.
So re-thinking it and researching, I discovered a way to change the [MAILCITY] field into XML nodes where each 'text segment' is assigned it's own Node within the xml field. Then those xml fields can be processed node by node, concatenated together, and then changed back to a SQL varchar. It's convoluted, but it works. :)
Here's the code:
CREATE TABLE
#masterfeelisting (
[MAILCITY] varchar(max) not null
);
INSERT INTO #masterfeelisting VALUES
('terra bellA')
,(' terrA novA ')
,('chicagO ')
,('bostoN')
,('porT dE sanTo')
,(' porT dE sanTo pallo ');
SELECT
RTRIM
(
(SELECT
UPPER([xmlField].[xmlNode].value('.', 'char(1)')) +
LOWER(STUFF([xmlField].[xmlNode].value('.', 'varchar(max)'), 1, 1, '')) + ' '
FROM [xmlNodeRecordSet].[nodeField].nodes('/N') as [xmlField]([xmlNode]) FOR
xml path(''), type
).value('.', 'varchar(max)')
) as [MAILCITY]
FROM
(SELECT
CAST('<N>' + REPLACE([MAILCITY],' ','</N><N>')+'</N>' as xml) as [nodeField]
FROM #masterfeelisting
) as [xmlNodeRecordSet];
Drop table #masterfeelisting;
First I create a table and fill it with dummy values.
Now here is the beauty of the code:
For each record in #masterfeelisting, we are going to create an xml field with a node for each 'text segment'.
ie. '<N></N><N>terrA</N><N>novA</N><N></N>'
(This is built from the varchar ' terrA novA ')
1) The way this is done is by using the REPLACE function.
The string starts with a '<N>' to designate the beginning of the node. Then:
REPLACE([MAILCITY],' ','</N><N>')
This effectively goes through the whole [MAILCITY] string and replaces each
' ' with '</N><N>'
and then the string ends with a '</N>'. Where '</N>' designates the end of each node.
So now we have a beautiful XML string with a couple of empty nodes and the 'text segments' nicely nestled in their own node. All the 'spaces' have been removed.
2) Then we have to CAST the string into xml. And we will name that field [nodeField]. Now we can use xml functions on our newly created record set. (Conveniently named [xmlNodeRecordSet].)
3) Now we can read the [xmlNodeRecordSet] into the main sub-Select by stating:
FROM [xmlNodeRecordSet].[nodeField].nodes('/N')
This tells us we are reading the [nodeField] as nodes with a '/N' delimiter.
This table of node fields is then parsed by stating:
as [xmlField]([xmlNode]) FOR xml path(''), type
This means each [xmlField] will be parsed for each [xmlNode] in the xml string.
4) So in the main sub-select:
Each blank node '<N></N>' is discarded. (Or not processed.)
Each node with a 'text segment' in it will be parsed. ie <N>terrA</N>
UPPER([xmlField].[xmlNode].value('.', 'char(1)')) +
This code will grab each node out of the field and take its contents '.' and only grab the first character 'char(1)'. Then it will Upper case that character. (the plus sign at the end means it will concatenate this letter with the next bit of code:
LOWER(STUFF([xmlField].[xmlNode].value('.', 'varchar(max)'), 1, 1, ''))
Now here is the beauty... STUFF is a function that will take a string, from a position, for a length, and substitute another string.
STUFF(string, start position, length, replacement string)
So our string is:
[xmlField].[xmlNode].value('.', 'varchar(max)')
Which grabs the whole string inside the current node since it is 'varchar(max)'.
The start position is 1. The length is 1. And the replacement string is ''. This effectively strips off the first character by replacing it with nothing. So the remaining string is all the other characters that we want to have lower case. So that's what we do... we use LOWER to make them all lower case. And this result is concatenated to our first letter that we already upper cased.
But wait... we are not done yet... we still have to append a + ' '. Which adds a blank space after our nicely capitalized 'text segment'. Just in case there is another 'text segment' after this node is done.
This main sub-Select will now parse each node in our [xmlField] and concatenate them all nicely together.
5) But now that we have one big happy concatenation, we still have to change it back from an xml field to a SQL varchar field. So after the main sub-select we need:
.value('.', 'varchar(max)')
This changes our [MAILCITY] back to a SQL varchar.
6) But hold on... we still are not done. Remember we put an extra space at the end of each 'text segment'??? Well the last 'text segment still has that extra space after it. So we need to Right Trim that space off by using RTRIM.
7) And dont forget to rename the final field back to as [MAILCITY]
8) And that's it. This code will take an unknown amount of 'text segments' and format each one of them. All using the fun of XML and it's node parsers.
Hope that helps :)
Here's one way to handle this using APPLY. Note that this solution supports up to 3 substrings (e.g. "Phoenix", "New York", "New York City") but can easily be updated to handle more.
DECLARE #string varchar(100) = 'nEW yoRk ciTY';
WITH DELIMCOUNT(String, DC) AS
(
SELECT #string, LEN(RTRIM(LTRIM(#string)))-LEN(REPLACE(RTRIM(LTRIM(#string)),' ',''))
),
CIPOS AS
(
SELECT *
FROM DELIMCOUNT
CROSS APPLY (SELECT CHARINDEX(char(32), string, 1)) CI1(CI1)
CROSS APPLY (SELECT CHARINDEX(char(32), string, CI1.CI1+1)) CI2(CI2)
)
SELECT
OldString = #string,
NewString =
CASE DC
WHEN 0 THEN UPPER(SUBSTRING(string,1,1))+LOWER(SUBSTRING(string,2,8000))
WHEN 1 THEN UPPER(SUBSTRING(string,1,1))+LOWER(SUBSTRING(string,2,CI1-1)) +
UPPER(SUBSTRING(string,CI1+1,1))+LOWER(SUBSTRING(string,CI1+2,100))
WHEN 2 THEN UPPER(SUBSTRING(string,1,1))+LOWER(SUBSTRING(string,2,CI1-1)) +
UPPER(SUBSTRING(string,CI1+1,1))+LOWER(SUBSTRING(string,CI1+2,CI2-(CI1+1))) +
UPPER(SUBSTRING(string,CI2+1,1))+LOWER(SUBSTRING(string,CI2+2,100))
END
FROM CIPOS;
Results:
OldString NewString
--------------- --------------
nEW yoRk ciTY New York City
This will only capitalize the first letter of the second word. A shorter but less flexible approach. Replace #str with [Mail City].
DECLARE #str AS VARCHAR(50) = 'Los angelas'
SELECT STUFF(#str, CHARINDEX(' ', #str) + 1, 1, UPPER(SUBSTRING(#str, CHARINDEX(' ', #str) + 1, 1)));
This is a way to use imbedded Selects for three City name parts.
It uses CHARINDEX to find the location of your separator character. (ie a space)
I put an 'if' structure around the Select to test if you have any records with more than 3 parts to the city name. If you ever get the warning message, you could add another sub-Select to handle another city part.
Although... just to be clear... SQL is not the best language to do complicated formatting. It was written as a data retrieval engine with the idea that another program will take that data and massage it into a friendlier look and feel. It may be easier to handle the formatting in another program. But if you insist on using SQL and you need to account for city names with 5 or more parts... you may want to consider using Cursors so you can loop through the variable possibilities. (But Cursors are not a good habit to get into. So don't do that unless you've exhausted all other options.)
Anyway, the following code creates and populates a table so you can test the code and see how it works. Enjoy!
CREATE TABLE
#masterfeelisting (
[MAILCITY] varchar(30) not null
);
Insert into #masterfeelisting select 'terra bella';
Insert into #masterfeelisting select ' terrA novA ';
Insert into #masterfeelisting select 'chicagO ';
Insert into #masterfeelisting select 'bostoN';
Insert into #masterfeelisting select 'porT dE sanTo';
--Insert into #masterfeelisting select ' porT dE sanTo pallo ';
Declare #intSpaceCount as integer;
SELECT #intSpaceCount = max (len(RTRIM(LTRIM([MAILCITY]))) - len(replace([MAILCITY],' ',''))) FROM #masterfeelisting;
if #intSpaceCount > 2
SELECT 'You need to account for more than 3 city name parts ' as Warning, #intSpaceCount as SpacesFound;
else
SELECT
cThird.[MAILCITY1] + cThird.[MAILCITY2] + cThird.[MAILCITY3] as [MAILCITY]
FROM
(SELECT
bSecond.[MAILCITY1] as [MAILCITY1]
,SUBSTRING(bSecond.[MAILCITY2],1,bSecond.[intCol2]) as [MAILCITY2]
,UPPER(SUBSTRING(bSecond.[MAILCITY2],bSecond.[intCol2] + 1, 1)) +
SUBSTRING(bSecond.[MAILCITY2],bSecond.[intCol2] + 2,LEN(bSecond.[MAILCITY2]) - bSecond.[intCol2]) as [MAILCITY3]
FROM
(SELECT
SUBSTRING(aFirst.[MAILCITY],1,aFirst.[intCol1]) as [MAILCITY1]
,UPPER(SUBSTRING(aFirst.[MAILCITY],aFirst.[intCol1] + 1, 1)) +
SUBSTRING(aFirst.[MAILCITY],aFirst.[intCol1] + 2,LEN(aFirst.[MAILCITY]) - aFirst.[intCol1]) as [MAILCITY2]
,CHARINDEX ( ' ', SUBSTRING(aFirst.[MAILCITY],aFirst.[intCol1] + 1, LEN(aFirst.[MAILCITY]) - aFirst.[intCol1]) ) as intCol2
FROM
(SELECT
UPPER (LEFT(RTRIM(LTRIM(mstr.[MAILCITY])),1)) +
LOWER(SUBSTRING(RTRIM(LTRIM(mstr.[MAILCITY])),2,LEN(RTRIM(LTRIM(mstr.[MAILCITY])))-1)) as [MAILCITY]
,CHARINDEX ( ' ', RTRIM(LTRIM(mstr.[MAILCITY]))) as intCol1
FROM
#masterfeelisting as mstr -- Initial Master Table
) as aFirst -- First Select Shell
) as bSecond -- Second Select Shell
) as cThird; -- Third Select Shell
Drop table #masterfeelisting;