Regex - Match all subsections - regex

I have the following strings which indicate a hierarchy:
JHW/1/24/3/562 // child row
JHW/1/24/3 // parent of the above
JHW/1/24 // parent of the above
JHW/1 // parent of the above
JHW // parent of the above
What I'd like to do is to be able to pull out all of the "parent" rows with one regex.
The closest I've got conceptually (which isn't anywhere near) is #^([^/]*/)+# which just matches the second-last section [eg. 3].
I've never really tried to do something like this before, where I'm trying to get overlapping results - it is possible? It's not an issue if it brings back the child row as one of the matches.

You can't obtain several results from the same position (since the regex engine go to the next character after each try of the pattern), you can explode the string with / and build each path you need with the array items.
An other possible way consists to reverse the string:
preg_match_all('~(?=(?:\A|/)(.+))~', strrev($path), $m);
$result = array_map('strrev', $m[1]);

Related

If cell contains '?' then formula X if not then copy value

At the moment I am busy with a spreadsheet to analyse results per url. The problem is that when I want to make a list of unique urls the urls with a parameter behind it (for example '?fbads') will be seen as unique, instead of that I need these results to be blended together with the main url. See example below:
https://www.holidayguru.nl/deal/accommodatie/luxe-strandvakantie-in-ijmuiden-5e25ba62-e001-4072-8eb5-b6c3b0e7e66f/?fbclid=IwA
&
https://www.holidayguru.nl/deal/accommodatie/luxe-strandvakantie-in-ijmuiden-5e25ba62-e001-4072-8eb5-b6c3b0e7e66f/
Should both be: https://www.holidayguru.nl/deal/accommodatie/luxe-strandvakantie-in-ijmuiden-5e25ba62-e001-4072-8eb5-b6c3b0e7e66f/
I already fixed this with a formula but I need one list with all urls. So I'm look for two options. Or in the
=LEFT(A11,FIND("?",A11)-1)
That I use right now I need to find a way how I can say. If you don't find a '?' than just copy cell A11
Or...
I have to work with an if fuction to say, if A11 contains '?' than execute =left fuction otherwise use A11.
I can't manage to get the formula working. Demo sheet is down below :). Thanks!
Example spreadsheet
Delete everything from Sheet1!A:A (including the header) and place the following in Sheet1!A1:
=ArrayFormula({"UNIQUE URLS"; UNIQUE(FILTER(REGEXEXTRACT(URLs!A2:A,"[^\?]+"),URLs!A2:A<>""))})
This will create the header (which you can change as you like within the formula itself) and a unique list of URLs as determined only by the portion before a question mark (if a question mark exists) or to the end of the original URL.
For your reference, the expression [^\?]+ means "a string of the greatest length that can be extracted without containing a literal question mark."
[ ] = "any of the characters contained herein"
[^ ] = "not any of these characters"
\ = literal marker (i.e., whatever is next will be treated as a literal character)
\? = literal question mark (using the literal marker before the ? is necessary, since alone, the ? has a separate special meaning in REGEX-type expressions)
+ = "one or more of the preceding character or group of characters"

parse URL params in Perl

I am working on some tutorials to explain things like GET/POST's and need to parse the URI manually. The follow perl code works, but I am trying to do two things:
list each key/value
be able to look up one specific value
What I do NOT care about is replacing the special chars to spaces or anything, the one value I need to get should be a number. In other languages I have used, the regular expression in question should group each key/value into one grouping with a part 1/part 2, does Perl do the same? If so, how do I put that into a map?
my #paramList = split /(?:\?|&|;)([^=]+)=([^&|;]+)/, $ENV{'REQUEST_URI'};
if(#paramList)
{
print "<h1>The Params</h1><ul>";
foreach my $i (#paramList) {
if($i) {
print "<li>$i</li>";
}
}
print "<ul>";
}
Per the request, here is a basic example of the input:
REQUEST_URI = /cgi-bin/printenv_html.pl?customer_name=fdas&phone_number=fdsa&email_address=fads%40fd.com&taxi=van&extras=tip&pickup_time=2020-01-14T20%3A45&pickup_place=&dropoff_place=Airport&comments=
goal is the following where the left of the equal is the key, and the right is the value:
customer_name=fdas
phone_number=fdsa
email_address=fads%40fd.com
taxi=van
extras=tip
pickup_time=2020-01-14T20%3A45
pickup_place=
dropoff_place=Airport
comments=
How about feeding your list of key-value pairs into a hash?
my %paramList = $ENV{'REQUEST_URI'} =~ /(?:\?|&|;)([^=]+)=([^&|;]+)/g;
(no reason for the split as far as I can tell)
This relies crucially on there being an even-sized list of matches, where each "before-=" thing becomes a key in the hash, with the value being its pairing "after-=" thing.
In order to also get "pairs" without a value (like comments=) change + in the last pattern to *

Extract text up to the Nth character in a string

How can I extract the text up to the 4th instance of a character in a column?
I'm selecting text out of a column called filter_type up to the fourth > character.
To accomplish this, I've been trying to find the position of the fourth > character, but it's not working:
select substring(filter_type from 1 for position('>' in filter_type))
You can use the pattern matching function in Postgres.
First figure out a pattern to capture everything up to the fourth > character.
To start your pattern you should create a sub-group that captures non > characters, and one > character:
([^>]*>)
Then capture that four times to get to the fourth instance of >
([^>]*>){4}
Then, you will need to wrap that in a group so that the match brings back all four instances:
(([^>]*>){4})
and put a start of string symbol for good measure to make sure it only matches from the beginning of the String (not in the middle):
^(([^>]*>){4})
Here's a working regex101 example of that!
Once you have the pattern that will return what you want in the first group element (which you can tell at the online regex on the right side panel), you need to select it back in the SQL.
In Postgres, the substring function has an option to use a regex pattern to extract text out of the input using a 'from' statement in the substring.
To finish, put it all together!
select substring(filter_type from '^(([^>]*>){4})')
from filter_table
See a working sqlfiddle here
If you want to match the entire string whenever there are less than four instances of >, use this regular expression:
^(([^>]*>){4}|.*)
You can also use a simple, non-regex solution:
SELECT array_to_string((string_to_array(filter_type, '>'))[1:4], '>')
The above query:
splits your string into an array, using '>' as delimeter
selects only the first 4 elements
transforms the array back to a string
substring(filter_type from '^(([^>]*>){4})')
This form of substring lets you extract the portion of a string that matches a regex pattern.
You can also split the string, then choose the N'th element inside the result list. For example:
SELECT SPLIT_PART('aa,bb,cc', ',', 2)
will return: bb.
This function is defined as:
SPLIT_PART(string, delimiter, position)
In order to look at this problem, I did the following (all of the code below is available on the fiddle here):
CREATE TABLE s
(
a TEXT
);
I then created a PL/pgSQL function to generate random strings as follows.
CREATE FUNCTION f() RETURNS TEXT LANGUAGE SQL AS
$$
SELECT STRING_AGG(SUBSTR('abcdef>', CEIL(RANDOM() * 7)::INTEGER, 1), '')
FROM GENERATE_SERIES(1, 40)
$$;
I got the code from here and modified it so that it would produce strings with lots of > characters for testing purposes.
I then manually inserted a few strings at the beginning so that a quick look would tell me if the code was working as anticipated.
INSERT INTO s VALUES
('afsad>adfsaf>asfasf>afasdX>asdffs>asfdf>'),
('23433>433453>4>4559>455>3433>'),
('adfd>adafs>afadsf>'), -- only 3 '>'s!
('babedacfab>feaefbf>fedabbcbbcdcfefefcfcd'),
('e>>>>>'), -- edge case - multiple terminal '>'s
('aaaaaaa'); -- edge case - no '>'s whatsoever
The reason I put in the records with fewer than 4 >s is because the accepted answer (see discussion at the end of this answer) puts forward a solution which should return the entire string if this is the case!
On the fiddle, I then added 50,000 records as follows:
INSERT INTO s
SELECT f() FROM GENERATE_SERIES(1, 50000);
I also created a table s on a home laptop (16GB RAM, 500MB NVMe SSD) and populated it with 40,000,000 (50M) records - times also shown.
Now, my reading of the question is that we need to extract the string up to but not including the 4th > character.
The first solution (from treecon) was this one (I also show them running on the fiddle, but to save space here, I've only included the partial output of EXPLAIN (ANALYZE, BUFFERS, VERBOSE)) - the times shown are typical over a few runs:
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT
ARRAY_TO_STRING((STRING_TO_ARRAY(a, '>'))[1:4], '>'),
a
FROM s;
Result (only key parts included):
Seq Scan on public.s
Execution Time: 81.807 ms
40M Time: 46 seconds
A regex solution which works (significantly faster):
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT
SUBSTRING(a FROM '^(?:[^>]*>){0,3}[^>]*'),
a
FROM s;
Result:
Seq Scan on public.s
Execution Time: 74.757 ms
40M Time: 32 seconds
The accepted answer fails on many levels (see the fiddle). It leaves a > at the end and fails on various strings even when modified. Also, the solution proposed to include strings with fewer than 4 >s (i.e. ^(([^>]*>){4}|.*)) merely returns the original string (see end of fiddle).

Split string and get last element

Let's say I have a column which has values like:
foo/bar
chunky/bacon/flavor
/baz/quz/qux/bax
I.e. a variable number of strings separated by /.
In another column I want to get the last element from each of these strings, after they have been split on /. So, that column would have:
bar
flavor
bax
I can't figure this out. I can split on / and get an array, and I can see the function INDEX to get a specific numbered indexed element from the array, but can't find a way to say "the last element" in this function.
Edit:
this one is simplier:
=REGEXEXTRACT(A1,"[^/]+$")
You could use this formula:
=REGEXEXTRACT(A1,"(?:.*/)(.*)$")
And also possible to use it as ArrayFormula:
=ARRAYFORMULA(REGEXEXTRACT(A1:A3,"(?:.*/)(.*)$"))
Here's some more info:
the RegExExtract function
Some good examples of syntax
my personal list of Regex Tricks
This formula will do the same:
=INDEX(SPLIT(A1,"/"),LEN(A1)-len(SUBSTITUTE(A1,"/","")))
But it takes A1 three times, which is not prefferable.
You could do this too
=index(SPLIT(A1, "/"), COLUMNS(SPLIT(A1, "/"))-1)
Also possible, perhaps best on a copy, with Find:
.+/
(Replace with blank) and Search using regular expressions ticked.
You can try use this!
You've got the array of String, so you can acess the last element by length
String message = "chunky/bacon/flavor";
String[] outSplited = message.split("/");
System.out.println(outSplited[outSplited.length -1]);

GetPositionAtOffset() don't return good position

I use a RichTextBox in WPF (4.0) and I use the GetPositionAtOffset() method to get a text range between two position in the content in RichTextBox.
1) I initialize the text pointer "position" from MyRichTextBox.Document.ContentStart :
TextPointer position = RTBEditor.Document.ContentStart;
2) I get the text from my RichTextBox like that :
var textRun = new TextRange(RTBEditor.Document.ContentStart, RTBEditor.Document.ContentEnd).Text;
3) With Regex I find a string that I want in textRun and get the begin's index and the end's index (I search a text between "/*" and "*/"):
Regex regex = new Regex(#"/\*([^\*/])*\*/");
var match = regex.Match(textRun);
TextPointer start = position.GetPositionAtOffset(matchBegin.Index, LogicalDirection.Forward);
TextPointer end = position.GetPositionAtOffset(matchBegin.Index + matchBegin.Length, LogicalDirection.Backward);
But, when I use these pointers in a textrange and colorize the text inside, it's not the good text matched in my regex (with goods indexes) which is colorized in my RichTextBox.
Why the GetPositionAtOffset() method don't give the position at the index specified ? It's this method the problem or it's somewhere else ?
Thank's for reply, I am stopped in my development.
According to this, https://msdn.microsoft.com/en-us/library/ms598662%28v=vs.110%29.aspx
GetPositionAtOffset returns a TextPointer to the position indicated by the specified offset, in symbols, from the beginning of the current TextPointer.
Any of the following is considered to be a symbol:
An opening or closing tag for the TextElement element.
A UIElement element contained in an InlineUIContainer or BlockUIContainer. Note that such a UIElement is always counted as exactly one symbol; any additional content or elements contained by the UIElement are not counted as symbols.
A 16-bit Unicode character inside of a text Run element.
Sorry to bother you, the problem was somewhere else.
I initialized the text of my RichTextBox with AppendText() method and not with a paragraph that I added in the blocks. So now it works fine !