Searching a substring in an string table? - string

I have an internal table with the following data (<fs_content>):
OFFER/005056B467AE1ED9B1962F12360477E9-A
OFFER/005056B467AE1ED9B1962F12360477E9-B
OFFER/005056B467AE1ED9B1962F12360477E9-C
OFFER/005056B467AE1ED9B1962F12360477E9-D
OFFER/005056B467AE1ED9B1962F12360477E9-E
I have to search repeatedly values like this (V1):
OFFER-A
OFFER-B
OFFER-C
OFFER-M
OFFER-L
I expect that the following values are identified, which match one line in the internal table (itab_v1_result):
OFFER-A
OFFER-B
OFFER-C
But as you can see in <fs_content> there's the same code 005056B467AE1ED9B1962F12360477E9, after OFFER/ until - symbol.
Now, I want to assign the rows from <fs_content> to field Symbol <fs_my_content> by comparing V1 value with each row in <fs_content>, but the problem is that V1 value is not completely same like <fs_content> rows.
I've tried to do something like this, but it's not working, <fs_my_content> is always empty:
READ TABLE <fs_content> ASSIGNING <fs_my_content> WITH KEY ('ATTR_NAME') = V1.
How can I get itab_v1_result to contain what I expect?
My minimal reproducible example:
TYPES:
BEGIN OF ty_content,
attr_name TYPE string,
END OF ty_content.
FIELD-SYMBOLS:
<fs_my_content> TYPE any,
<fs_content> TYPE ANY TABLE.
DATA:
itab_content TYPE STANDARD TABLE OF ty_content,
itab_v1 TYPE STANDARD TABLE OF string,
itab_v1_result TYPE STANDARD TABLE OF string,
v1 TYPE string.
itab_content = VALUE #(
( attr_name = 'OFFER/005056B467AE1ED9B1962F12360477E9-A' )
( attr_name = 'OFFER/005056B467AE1ED9B1962F12360477E9-B' )
( attr_name = 'OFFER/005056B467AE1ED9B1962F123604D7E9-C' )
( attr_name = 'OFFER/005056B467AE1ED9B1962F12360477E9-D' )
( attr_name = 'OFFER/005056B467AE1ED9B1962F12360477E9-E' ) ).
itab_v1 = VALUE #(
( `OFFER-A` )
( `OFFER-B` )
( `OFFER-C` )
( `OFFER-M` )
( `OFFER-L` ) ).
ASSIGN itab_content TO <fs_content>.
LOOP AT itab_v1 INTO v1.
READ TABLE <fs_content> ASSIGNING <fs_my_content> WITH KEY ('ATTR_NAME') = v1.
IF sy-subrc = 0.
APPEND v1 TO itab_v1_result.
ENDIF.
ENDLOOP.
" Here, itab_v1_result is empty unfortunately!?

You cannot use any operators other than = in READ TABLE. But you can use them in a LOOP.
First you'd have to arrange your V1 in a way that CS can identify, so just use the '-X', which seems to be unique. Then you can use your condition in the LOOP clause.
offset = STRLEN( v1 ) - 2.
v2 = v1+offset(2).
LOOP AT itab1 ASSIGNING <fs_itab1> WHERE attribute_name CS v2.
" do something
" if you only want to do it for the first entry you find, then just EXIT afterwards
ENDLOOP.

You are over-complicating the solution. Why not just use substring access?
LOOP AT itab_v1 INTO v1.
LOOP AT itab_content ASSIGNING FIELD-SYMBOL(<content>).
CHECK v1(5) = <content>-attr_name(5) AND substring( val = v1 off = strlen( v1 ) - 1 len = 1 ) = substring( val = <content>-attr_name off = strlen( <content>-attr_name ) - 1 len = 1 ).
APPEND v1 TO itab_v1_result.
ENDLOOP.
ENDLOOP.

Thanks a lot to all of you for your variants of solution. It was very helpful for me.
Here's the solution of my problem.
First of all we should loop at <fs_content> and assign it to new field-symbol <dynamic_content>.
Then, we should get ATTR_NAME field from <dynamic_content> and assign it to another field-symbol <contact_attribute_name>.
We'll use some function for working with STRING type value, because of this we'll assign <contact_attribute_name> to lv_attr_name.
As we know (from task description) in lv_attr_name would be the values like: OFFER/005056B467AE1ED9B1962F12360477E9-A and so on.
Because of this we'll find the position of first / by find() method from the beginning of lv_attr_name and put the value into lv_slash_position.
We repeat this operation for finding the position of first - after lv_slash_position and put the value into lv_dash_position.
After this two operation we'll use the replace() method and replace lv_dash_position - lv_slash_position to empty value. In the end we'll get OFFER/-A and put it into lv_attr_val_string.
In the end we'll compare lv_attr_val_string and v1, if lv_attr_val_string <> v1 we would not put it to the final itab itab_v1_result, else we'll do it.
LOOP AT <fs_content> ASSIGNING <dynamic_content>.
ASSIGN COMPONENT 'ATTR_NAME' OF STRUCTURE <dynamic_content> TO <contact_attribute_name>.
DATA(lv_attr_name) = CONV string( <contact_attribute_name> ).
DATA(lv_slash_position) = find( val = lv_attr_val_string
sub = '/'
off = 0 ).
IF lv_slash_position <> '1-'.
DATA(lv_dash_position) = find( val = lv_attr_val_string
sub = '-'
off = lv_slash_position ).
lv_attr_val_string = replace( val = lv_attr_val_string
off = lv_slash_position
len = ( lv_dash_position - lv_slash_position )
with = '' ).
ENDIF.
IF lv_attr_val_string <> v1.
APPEND v1 TO itab_v1_result.
CONTINUE.
ENDIF.
ENDLOOP.

Related

How to separate a string by Capital Letter?

I currently have to a code in ABAP which contains a String that has multiple words that start with Capital letters/Uppercase and there is no space in-between.
I have to separate it into an internal table like this:
INPUT :
NameAgeAddress
OUTPUT :
Name
Age
Address
Here is the shortest code I could find, which uses a regular expression combined with SPLIT:
SPLIT replace( val = 'NameAgeAddress' regex = `(?!^.)\u` with = ` $0` occ = 0 )
AT ` `
INTO TABLE itab.
So, replace converts 'NameAgeAddress' into 'Name Age Address' and SPLIT puts the 3 words into an internal table.
Details:
(?!^.) to say the next character to find (\u) should not be the first character
\u being any upper case letter
$0 to replace the found string ($0) by itself preceded with a space character
occ = 0 to replace all occurrences
Unfortunately, the SPLIT statement in ABAP does not allow a regex as separator expression. Therefore, we have to use progressive matching, which is a bit awkward in ABAP:
report zz_test_split_capital.
parameters: p_input type string default 'NameAgeAddress' lower case.
data: output type stringtab,
off type i,
moff type i,
mlen type i.
while off < strlen( p_input ).
find regex '[A-Z][^A-Z]*'
in section offset off of p_input
match offset moff match length mlen.
if sy-subrc eq 0.
append substring( val = p_input off = moff len = mlen ) to output.
off = moff + mlen.
else.
exit.
endif.
endwhile.
cl_demo_output=>display_data( output ).
Just for comparison, the following statement would do the job in Perl:
my $input = "NameAgeAddress";
my #output = split /(?=[A-Z])/, $input;
# gives #output = ('Name','Age','Address')
It is easy with using regular expressions. The solution could look like this.
REPORT ZZZ.
DATA: g_string TYPE string VALUE `NameAgeAddress`.
DATA(gcl_regex) = NEW cl_abap_regex( pattern = `[A-Z]{1}[a-z]+` ).
DATA(gcl_matcher) = gcl_regex->create_matcher( text = g_string ).
WHILE gcl_matcher->find_next( ).
DATA(g_match_result) = gcl_matcher->get_match( ).
WRITE / g_string+g_match_result-offset(g_match_result-length).
ENDWHILE.
For when regular expressions are just overkill and plain old ABAP will do:
DATA(str) = 'NameAgeAddress'.
IF str CA sy-abcde.
DATA(off) = 0.
DO.
data(tailstart) = off + 1.
IF str+tailstart CA sy-abcde.
DATA(len) = sy-fdpos + 1.
WRITE: / str+off(len).
add len to off.
ELSE.
EXIT.
ENDIF.
ENDDO.
write / str+off.
ENDIF.
If you do not want to use or cannot use Regex, here another solution:
DATA: lf_input TYPE string VALUE 'NameAgeAddress',
lf_offset TYPE i,
lf_current_letter TYPE char1,
lf_letter_in_capital TYPE char1,
lf_word TYPE string,
lt_word LIKE TABLE OF lf_word.
DO strlen( lf_input ) TIMES.
lf_offset = sy-index - 1.
lf_current_letter = lf_input+lf_offset(1).
lf_letter_in_capital = to_upper( lf_current_letter ).
IF lf_current_letter = lf_letter_in_capital.
APPEND INITIAL LINE TO lt_word ASSIGNING FIELD-SYMBOL(<ls_word>).
ENDIF.
IF <ls_word> IS ASSIGNED. "if input string does not start with capital letter
<ls_word> = <ls_word> && lf_current_letter.
ENDIF.
ENDDO.

Looping string characters?

How can I read each character in a String? For example, I want to read each character in String "a7m4d0". After that I want to verify that each character is a character or a number. Any tips or ideas?
DATA: smth TYPE string VALUE `qwert1yua22sd123bnm,`,
index TYPE i,
length TYPE i,
char TYPE c,
num TYPE i.
length = STRLEN( smth ).
WHILE index < length.
char = smth+index(1).
TRY .
num = char.
WRITE: / num,'was a number'.
CATCH cx_sy_conversion_no_number.
WRITE: / char,'was no number'.
ENDTRY.
ADD 1 TO index.
ENDWHILE.
Here's your problem solved :P
A bit convoluted and on a recent 740 ABAP server. :)
DATA: lv_text TYPE string VALUE `a7m4d0`.
DO strlen( lv_text ) TIMES.
DATA(lv_single) = substring( val = lv_text off = sy-index - 1 len = 1 ) && ` is ` &&
COND string( WHEN substring( val = lv_text off = sy-index - 1 len = 1 ) CO '0123456789' THEN 'Numeric'
ELSE 'Character' ).
WRITE : / lv_single.
ENDDO.
Here is how you can access a single character within a string:
This example will extract out the character "t" into the variable "lv_char1".
DATA: lv_string TYPE char10,
lv_char TYPE char1.
lv_string = "Something";
lv_char1 = lv_string+4(1).
Appending "+4" to the string name specifies the offset from the start of the string (in this case 4), and "(1)" specifies the number of characters to pick up.
See the documentation here for more info:
http://help.sap.com/saphelp_nw04/Helpdata/EN/fc/eb341a358411d1829f0000e829fbfe/content.htm
If you want to look at each character in turn, you could get the length of the field using "strlen( )" and do a loop for each character.
One more approach
PERFORM analyze_string USING `qwert1yua22sd123bnm,`.
FORM analyze_string USING VALUE(p_string) TYPE string.
WHILE p_string IS NOT INITIAL.
IF p_string(1) CA '0123456798'.
WRITE: / p_string(1) , 'was a number'.
ELSE.
WRITE: / p_string(1) , 'was no number'.
ENDIF.
p_string = p_string+1.
ENDWHILE.
ENDFORM.
No DATA statements, string functions or explicit indexing required.
I know the post it's old but this might be useful, this is what use :)
DATA lv_counter TYPE i.
DO STRLEN( lv_word ) TIMES.
IF lv_word+lv_counter(1) CA '0123456789'
"It's a number
ENDIF.
lv_counter = lv_counter + 1.
ENDDO.

Is it possible to concatenate a string with series of number?

I have a string (eg. 'STA') and I want to make a cell array that will be a concatenation of my sting with a numbers from 1 to X.
I want the code to do something like the fore loop here below:
for i = 1:Num
a = [{a} {strcat('STA',num2str(i))}]
end
I want the end results to be in the form of {<1xNum cell>}
a = 'STA1' 'STA2' 'STA3' ...
(I want to set this to a uitable in the ColumnFormat array)
ColumnFormat = {{a},... % 1
'numeric',... % 2
'numeric'}; % 3
I'm not sure about starting with STA1, but this should get you a list that starts with STA (from which I guess you could remove the first entry).
N = 5;
[X{1:N+1}] = deal('STA');
a = genvarname(X);
a = a(2:end);
You can do it with combination of NUM2STR (converts numbers to strings), CELLSTR (converts strings to cell array), STRTRIM (removes extra spaces)and STRCAT (combines with another string) functions.
You need (:) to make sure the numeric vector is column.
x = 1:Num;
a = strcat( 'STA', strtrim( cellstr( num2str(x(:)) ) ) );
As an alternative for matrix with more dimensions I have this helper function:
function c = num2cellstr(xx, varargin)
%Converts matrix of numeric data to cell array of strings
c = cellfun(#(x) num2str(x,varargin{:}), num2cell(xx), 'UniformOutput', false);
Try this:
N = 10;
a = cell(1,N);
for i = 1:N
a(i) = {['STA',num2str(i)]};
end

Split a string and store in an array in lua

I need to split a string and store it in an array. here i used string.gmatch method, and its splitting the characters exactly, but my problem is how to store in an array ? here is my script.
my sample string format : touchedSpriteName = Sprite,10,rose
objProp = {}
for key, value in string.gmatch(touchedSpriteName,"%w+") do
objProp[key] = value
print ( objProp[2] )
end
if i print(objProp) its giving exact values.
Your expression returns only one value. Your words will end up in keys, and values will remain empty. You should rewrite the loop to iterate over one item, like this:
objProp = { }
touchedSpriteName = "touchedSpriteName = Sprite,10,rose"
index = 1
for value in string.gmatch(touchedSpriteName, "%w+") do
objProp[index] = value
index = index + 1
end
print(objProp[2])
This prints Sprite (link to demo on ideone).
Here's a nice function that explodes a string into an array. (Arguments are divider and string)
-- Source: http://lua-users.org/wiki/MakingLuaLikePhp
-- Credit: http://richard.warburton.it/
function explode(div,str)
if (div=='') then return false end
local pos,arr = 0,{}
for st,sp in function() return string.find(str,div,pos,true) end do
table.insert(arr,string.sub(str,pos,st-1))
pos = sp + 1
end
table.insert(arr,string.sub(str,pos))
return arr
end
Here is a function that i made:
function split(str, character)
result = {}
index = 1
for s in string.gmatch(str, "[^"..character.."]+") do
result[index] = s
index = index + 1
end
return result
end
And you can call it :
split("dog,cat,rat", ",")
Reworked code of Ricardo:
local function split (string, separator)
local tabl = {}
for str in string.gmatch(string, "[^"..separator.."]+") do
table.insert (tabl, str)
end
return tabl
end
print (unpack(split ("1234#5678#9012", "#")))
-- returns 1234 5678 9012

T-SQL Split Word into characters

I have searched everywhere and I cannot find this implementation anywhere.
Let's say I have the word: QWERTY
I want to obtain this table:
Q
W
E
R
T
Y
Or for QWERTY AnotherWord I want to obtain
Q
W
E
R
T
Y
[space character here]
A
n
o
t
h
e
r
W
o
r
d
Do it like this:
select substring(a.b, v.number+1, 1)
from (select 'QWERTY AnotherWord' b) a
join master..spt_values v on v.number < len(a.b)
where v.type = 'P'
Declare #word nvarchar(max)
Select #word = 'Hello This is the test';
with cte (Number)as
(Select 1
union all
select Number +1 From cte where number <len(#word)
)
select * from Cte Cross apply (Select SUBSTRING(#word,number,1 ) ) as J(Letter)
Here you have it:
create table #words (
character varchar(1)
)
declare #test varchar(10)
select #test = 'QWERTY'
declare #count int, #total int
select #total = len(#test), #count = 0
while #count <= #total
begin
insert into #words select substring(#test, #count, 1)
select #count = #count + 1
end
select * from #words
drop table #words
Here is a table-valued function (derived from aF's temp table implementation). It differs slightly from aF's implementation in that it starts with #count=1; this excludes an extraneous leading space.
CREATE FUNCTION [dbo].[Chars] (#string VARCHAR(max))
RETURNS #chars TABLE (character CHAR)
AS
BEGIN
DECLARE #count INT,
#total INT
SELECT #total = Len(#string),
#count = 1
WHILE #count <= #total
BEGIN
INSERT INTO #chars
SELECT Substring(#string, #count, 1)
SELECT #count = #count + 1
END
RETURN
END
Usage:
SELECT * FROM dbo.chars('QWERTY AnotherWord')
Please, PLEASE avoid referencing systems tables, specifically system tables in system databases. In fact, the selected answer above probably won't compile in a Visual Studio 2013 Database Project
Table variables are fine, but recursion with a CTE is the answer:
DECLARE #str VARCHAR(max)
SET #str = 'QWERTY AnotherWord'
WITH Split(stpos,endpos)
AS(
SELECT 1 AS stpos, 2 AS endpos
UNION ALL
SELECT endpos, endpos+1
FROM Split
WHERE endpos <= LEN(#str)
)
SELECT
'character' = SUBSTRING(#str,stpos,COALESCE(NULLIF(endpos,0),LEN(#str)+1)-stpos)
,'charindex' = stpos
FROM Split
That said, the use for the code above is to get a table full of letters representing different permissions for a user. That is not the way to do this. Make a table with an ID, a permission code and a description then make a linking table between the users table and the new permissions table. this gives you the same abilities and doesn't make you solve dumb problems like this.
I wanted to contribute my own solution to this problem.
Convert into table valued function as desired (and handle nulls however you wish)
DECLARE #str nvarchar(100) = 'QWERTY AnotherWord'
DECLARE #len int = LEN(#str)-1;
--create a string of len(#str)-1 commas
--because STRING_SPLIT will return n rows for n-1 commas
--split string to return a table of len(#str) rows
--provide an index column named [index]
WITH [rows] AS (
SELECT
ROW_NUMBER() OVER (ORDER BY [value]) [index]
FROM STRING_SPLIT(REPLICATE(',', #len), ',')
),
--for each row, take the index number
--and extract the character from that index
[split] AS (
SELECT
[index],
SUBSTRING(#str,[index],1) [char]
FROM [rows]
)
--maintain the same order
--and return just the extracted characters
SELECT
--[index],
[char]
FROM [split]
ORDER BY [index] ASC
output:
char
----
Q
W
E
R
T
Y
A
n
o
t
h
e
r
W
o
r
d
I like the use of REPLICATE() and substring in the answer by #drrollergator. I find value in the answer below, in accounting for:
The truncation to 8000 characters mentioned by Microsoft learn/docs. Explicitly casting to a larger datatype will avoid this.
the unordered ROW_NUMBER as mentioned in [https://stackoverflow.com/questions/44105691/row-number-without-order-by].
Sample SQL:
DECLARE #str NVARCHAR(MAX) = N'QWERTY AnotherWord'
SELECT
ss.[value]
FROM
( SELECT TOP(LEN(#str))
SUBSTRING(#str,n.[i],1) [value]
,n.[i]
FROM ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT '.')) [i] FROM STRING_SPLIT(REPLICATE(CAST('.' AS VARCHAR(MAX)),LEN(#str) - 1),'.') ) n([i])
/* [A.] Generate numbers equal to character count in #expression */
ORDER BY n.[i]
/* [B.] Return 1-Char-Substring for each number/position */
) ss

Resources