It is currently August 8th, 2020, 5:12 am

Number of character or phrase included in a string

General topics related to Rainmeter.
mak_kawa
Posts: 773
Joined: December 30th, 2015, 9:47 am

Re: Number of character or phrase included in a string

Post by mak_kawa »

Now maybe I almost figured out the problem using "two-stage" substitution, but not perfect (see below).

Code: Select all

Substitute="#SearchString#":"~","[^~]":"","~":"9"
Substitute="#SearchString#":"~","[^~]":"","~":"+1"
As you see, this substitution doesn't work if the target string contains "~" character. It seems to be a rare case so ignorable, really?? :-)
Maybe there should be a perfect solution, but I don't reach.

BTW, to say again, I love the Lua way without no such problem.
User avatar
Yincognito
Posts: 2195
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Number of character or phrase included in a string

Post by Yincognito »

Note: The code below has some fringe situations where it doesn't work, but I'll correct it later today. Nevermind, it was just the need to enclose the variables values between quotes (for space reasons and such). Other than that, never use the empty string as value for the SearchFor variable.
mak_kawa wrote:
July 12th, 2020, 3:00 am
Now maybe I almost figured out the problem using "two-stage" substitution, but not perfect (see below).

Code: Select all

Substitute="#SearchString#":"~","[^~]":"","~":"9"
Substitute="#SearchString#":"~","[^~]":"","~":"+1"
As you see, this substitution doesn't work if the target string contains "~" character. It seems to be a rare case so ignorable, really?? :-)
Maybe there should be a perfect solution, but I don't reach.

BTW, to say again, I love the Lua way without no such problem.
Here is your perfect solution, based on the string length code balala mentioned earlier (if you have other places where you need perfection or folks label it impossible, just call me, I can't resist those challenges, LOL).

Code:

Code: Select all

[Variables]
; String to search into
SearchInto="Hello, world#CRLF#The world is a big place.#CRLF#World Wonders are great places to visit."
; String to search for (never use the empty string, i.e. "", here!)
SearchFor="o"
; Whether or not the search should be case sensitive ("i" = case insensitive, "" = case sensitive)
MatchCase=""

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1
BackgroundMode=2
SolidColor=47,47,47,255

---Measures---

[MeasureNumberOfOccurencesString]
Measure=String
String=#SearchInto#
UpdateDivider=-1
RegExpSubstitute=1
Substitute="(?s#MatchCase#U).*#SearchFor#":"#SearchFor#","(?s#MatchCase#)^((?:#SearchFor#)*).*$":"\1","(?s#MatchCase#)(?:^\\\d+|\\\d+$)":"","(?s#MatchCase#)#SearchFor#":"+1"
DynamicVariables=1

[MeasureNumberOfOccurencesCalc]
Measure=Calc
Formula=[MeasureNumberOfOccurencesString]
DynamicVariables=1

---Meters---

[MeterNumberOfOccurences]
Meter=STRING
FontFace=Consolas
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
FontSize=9
AntiAlias=1
ClipString=2
ClipStringW=300
MeasureName=MeasureNumberOfOccurencesCalc
Text="Search  For:#CRLF#'#SearchFor#'#CRLF##CRLF#Search Into:#CRLF#'#SearchInto#'#CRLF##CRLF#Occurences =#CRLF#%1"
DynamicVariables=1
Result:
Occurences In String.jpg
I made extensive use of variables in the substitution, in order to allow case matching just by modifying the corresponding variable in the [Variables] section, and allow space, new line or tab characters in both strings, so the substitution looks more complicated than it really is.

To see how simple it is, let's assume we have to look for the number of o occurences in Hello World. Getting rid of variables and flags like (?s) or (?i) to see what's going on leads to:

Code: Select all

Substitute="(?U).*o":"o","^((?:o)*).*$":"\1","(?:^\\\d+|\\\d+$)":"","o":"+1"
Now it's easier to understand what happens (each comma separated substitution explained below):

- we replace string parts that end with 'o' with 'o', in effect making the start of the string we look into to have as many 'o' as there are 'o' occurences, basically turning it into oorld. This happens because the pattern is ungreedy, matching as few characters as possible with the *, and also because the substitute being global as implied in the manual, the next substitution / search for a pattern begins where the "cursor" was last positioned in the string, i.e. from where the last substitution occured. In other words, internally it's like 'Hello World', where the 1st replaced part (green) is not being searched again when looking for the 2nd replaced part (blue).
- we then replace the resulting string with the set of starting 'o'-s, so it becomes oo
- because in Rainmeter a capture may not return an empty string as explained in the manual, we have to remove the possible \1 leftovers, if by any chance the string we look for has not been found and the previous substitution returned the literal \1 instead of the empty string (not the case here, obviously).
- finally, since the string now became a set of 'o'-s, we can safely replace every of those 'o'-s with +1, so that our formula string would yield the right result in the following measure.

That's about it. Have fun using Lua needlessly, though. :lol:
You do not have the required permissions to view the files attached to this post.
Last edited by Yincognito on July 12th, 2020, 11:36 am, edited 3 times in total.
mak_kawa
Posts: 773
Joined: December 30th, 2015, 9:47 am

Re: Number of character or phrase included in a string

Post by mak_kawa »

Hi Yincognito

Thanks a lot for input.

Wow...perfect indeed! For a RegExp noob as me, it causes headache though...:-)
I will call you next time as "Mr. Perfection", lol.

BTW, the world of regular expression is really deep.
User avatar
Yincognito
Posts: 2195
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Number of character or phrase included in a string

Post by Yincognito »

mak_kawa wrote:
July 12th, 2020, 11:32 am
Hi Yincognito

Thanks a lot for input.

Wow...perfect indeed! For a RegExp noob as me, it causes headache though...:-)
I will call you next time as "Mr. Perfection", lol.

BTW, the world of regular expression is really deep.
Haha, no need to - even though I wouldn't mind the moniker, one has to be humble enough in his quest for perfection (which does exist) and making the "impossible" be possible. It's all about our minds' ability to get better, no big deal, and it's in everyone's reach.

P.S. Make sure you read the note at the start of my previous reply.
User avatar
jsmorley
Developer
Posts: 21217
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Number of character or phrase included in a string

Post by jsmorley »

I think there might be a somewhat simpler function in Lua to do this for you...

Skin:

Code: Select all

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1

[Variables]
SearchInto=Hello, world#CRLF#The "world" is a 'big' place.#CRLF#World Wonders are great places to visit.
SearchFor=o

---Measures---

[Lua]
Measure=Script
ScriptFile=Test.lua
Disabled=1

---Meters---

[MeterNumberOfOccurences]
Meter=String
FontFace=Consolas
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
FontSize=9
AntiAlias=1
ClipString=2
ClipStringW=300
Text=There are [&Lua:Count('#SearchInto#','#SearchFor#')] intances of "#SearchFor#" in the string "#SearchInto#"
DynamicVariables=1
Test.lua

Code: Select all

function Count(stringArg, searchArg)

	stringArg = string.lower(stringArg)
	searchArg = string.lower(searchArg)

	tempString, instanceCount = string.gsub(stringArg, searchArg, '')
	
	return instanceCount

end

1.jpg


This works because string.gsub(), which is Lua's "search and replace" function, returns two values. First, the string with the replacements done, which in this case you don't need, and second, the number of times it did the replacement in the string, aka the "instances" of the search string within the target string.

Quotes are a non-issue, length of the string is a non-issue, trying to avoid any particular character is a non-issue.

I'm all for Substitute / regular expression, it is very powerful and flexible. There are times when it is certainly the right answer. This, in my view, isn't one of them. I simply don't understand why I would want to use a hugely complicated regular expression, and a Calc measure, to more or less "re-invent the wheel", when there is a very simple, easy to understand and follow, function() built into Lua that does exactly what you are asking for. To each his own, and I can see the charm in the challenge of this, but it's not how I would go with this personally.

Feel free to add another parameter for "case sensitivity" to the arguments to the Count() function if you need it. if you desire case sensitivity, just skip the string.lower() calls entirely. Lua is always, always case sensitive.


Extra Credit

Note that while any characters, including the pattern matching reserved characters of ( ) % . + - * [ ? ^ $ are fine in the "target" string to search, some care must be taken when using them in the "search" string, when they might well be taken as commands to pattern matching, instead of a literal. In that case, you should use the pattern matching % escape character to delineate them.

So for instance:

Code: Select all

SearchInto=^^^^Hello World^^^^
SearchFor=^
Would return "1" as the answer, as the search string is being interpreted as "how many times does the string start?".

Code: Select all

SearchInto=^^^^Hello World^^^^
SearchFor=%^
Would properly return "8" as the answer, as the ^ is treated as a literal in the search string.

One other little tip is that if you wanted to search for "the" and not get a hit on for instance "theater", you can use:

SearchFor=[\x20]the[\x20]

Since Rainmeter will throw away any starting and ending "quotes" on an option, it won't help to enclose the string in quotes like " the ", as Rainmeter will first throw away the quotes, and then truncate any trailing spaces. Forcing the spaces with Character Variables will solve this.
You do not have the required permissions to view the files attached to this post.
User avatar
jsmorley
Developer
Posts: 21217
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Number of character or phrase included in a string

Post by jsmorley »

Code: Select all

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1

[Variables]
SearchInto=Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.#CRLF##CRLF#Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure. We are met on a great battlefield of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.#CRLF##CRLF#But in a larger sense, we cannot dedicate, we cannot consecrate, we cannot hallow, this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember, what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us - that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion - that we here highly resolve that these dead shall not have died in vain - that this nation, under God, shall have a new birth of freedom - and that government of the people, by the people, for the people, shall not perish from the earth.
SearchFor=[\x20]we[\x20]

---Measures---

[Lua]
Measure=Script
ScriptFile=Test.lua
Disabled=1

---Meters---

[MeterNumberOfOccurences]
Meter=String
FontColor=255,255,255,255
SolidColor=47,47,47,255
Padding=5,5,5,5
FontSize=11
AntiAlias=1
ClipString=2
ClipStringW=320
InlineSetting=Weight | 700
InlinePattern=(?i)^There are (.*) instances.*$
InlineSetting2=Color | 132,255,135,255
InlinePattern2=(?i)^There are (.*) instances.*$
InlineSetting3=Weight | 700
InlinePattern3=(?i)(#SearchFor#)
InlineSetting4=Color | 154,187,255,255
InlinePattern4=(?i)(#SearchFor#)
Text=There are [&Lua:Count('#SearchInto#','#SearchFor#')] instances of "#SearchFor#" in the string#CRLF##CRLF#"#SearchInto#"
DynamicVariables=1

1.jpg
You do not have the required permissions to view the files attached to this post.
mak_kawa
Posts: 773
Joined: December 30th, 2015, 9:47 am

Re: Number of character or phrase included in a string

Post by mak_kawa »

Hi jsmorley

"string.gsub"... new to me. If I can handle this function, the "for do" loop is unnecessary anymore. It might be more smart...
So, I tried it and works. I am not sure about its performance advantage or so, but thanks a lot for cool professional input.
User avatar
jsmorley
Developer
Posts: 21217
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Number of character or phrase included in a string

Post by jsmorley »

mak_kawa wrote:
July 13th, 2020, 1:01 am
Hi jsmorley

"string.gsub"... new to me. If I can handle this function, the "for do" loop is unnecessary anymore. It might be more smart...
So, I tried it and works. I am not sure about its performance advantage or so, but thanks a lot for cool professional input.
All of Lua is very, very efficient, as it is a language that I like to think of as "close to the metal", just two steps removed from machine language. It is entirely written in vanilla C language, and has almost no "bloat" at all. As to string.gsub() specifically, in all due fairness to Yincognito, string.gsub() is almost a perfect functional equivalent to RegExpSubstitute / Substitute in Rainmeter. Both have the same purpose. Both do a "global search and replace" on a string. The advantage to string.gsub() in this specific case is that it has the native ability to return the answer to "how many times did I do that?" as a part of the function. It's what makes it just falling-down easy to use for "counting" how many times a character or word or phrase appears in a string.

Other than that, it has no particular advantage over Substitute in Rainmeter, and I generally wouldn't jump out to Lua just to extract or replace a string. Once again in all fairness, Substitute with PCRE regular expression is in fact a more powerful tool than Pattern Matching in Lua.

Over time, we have tried to make it more and more possible to stay in native Rainmeter code, in instances where perhaps Lua was the only practical solution in the past. Examples of this are IfCondition, IfMatch, and InlineSetting/InlinePattern.

Generally, I lean toward Lua when what I am trying to do is very "procedural" in nature, for / next, do / while, if/then/elseif/then/else/then and such, as Rainmeter is not a procedural programming language, or when I find a need for using "arrays", what Lua calls "tables". Lua has capabilities in those areas that just can't reasonably be replicated in Rainmeter code. I also tend to glance Lua's way when something "repetitive" needs to be done. If I have 100 strings that all need to have "cat" replaced with "dog" in them, I'm not going to be fond of having 100 identical RegExpSubstitute / Substitute options. I'm going to be tempted to write ONE string.gsub() in a function in Lua, and just call it 100 times with Inline Lua, right in the meters where I want to use them.
User avatar
Yincognito
Posts: 2195
Joined: February 27th, 2015, 2:38 pm
Location: Terra Yincognita

Re: Number of character or phrase included in a string

Post by Yincognito »

jsmorley wrote:
July 13th, 2020, 2:09 am
All of Lua is very, very efficient, as it is a language that I like to think of as "close to the metal", just two steps removed from machine language. It is entirely written in vanilla C language, and has almost no "bloat" at all. As to string.gsub() specifically, in all due fairness to Yincognito, string.gsub() is almost a perfect functional equivalent to RegExpSubstitute / Substitute in Rainmeter. Both have the same purpose. Both do a "global search and replace" on a string. The advantage to string.gsub() in this specific case is that it has the native ability to return the answer to "how many times did I do that?" as a part of the function. It's what makes it just falling-down easy to use for "counting" how many times a character or word or phrase appears in a string.

Other than that, it has no particular advantage over Substitute in Rainmeter, and I generally wouldn't jump out to Lua just to extract or replace a string. Once again in all fairness, Substitute with PCRE regular expression is in fact a more powerful tool than Pattern Matching in Lua.

Over time, we have tried to make it more and more possible to stay in native Rainmeter code, in instances where perhaps Lua was the only practical solution in the past. Examples of this are IfCondition, IfMatch, and InlineSetting/InlinePattern.

Generally, I lean toward Lua when what I am trying to do is very "procedural" in nature, for / next, do / while, if/then/elseif/then/else/then and such, as Rainmeter is not a procedural programming language, or when I find a need for using "arrays", what Lua calls "tables". Lua has capabilities in those areas that just can't reasonably be replicated in Rainmeter code. I also tend to glance Lua's way when something "repetitive" needs to be done. If I have 100 strings that all need to have "cat" replaced with "dog" in them, I'm not going to be fond of having 100 identical RegExpSubstitute / Substitute options. I'm going to be tempted to write ONE string.gsub() in a function in Lua, and just call it 100 times with Inline Lua, right in the meters where I want to use them.
Yeah, I refrained from replying on your previous posts, as I didn't want to go on and start another inflamatory debate over the abilities and strengths of Regex in bare Rainmeter code and Lua. I actually don't mind such debates, as I think I'm well prepared with arguments in every situation, but doing this again and again is probably not going to be pleasant for the discussion partner. It turns out I was right to do that, since you said pretty much what I would have said (though probably more to the point) if I would have posted my reply. One small note though, I completely disagree with you labeling (yet again, LOL) one of my code samples as "hugely complicated" - I mean that was just 12 lines of code for 2 measures, and just 4 comma separated substitutions in a single option; if that was "complicated", what about your code / substitutions / RegExp options in the Weather.com megaskin (just an example)? :confused:

Anyway, I agree with you on this post, but I have another small note to make - please bear with me. The problem does not lie in needing to have 100 identical RegExpSubstitute / Substitute options for 100 strings that all need to have "cat" replaced with "dog" in them. That is a non isssue, as it can be done in a single (or in this specific case, two, if we count the Calc one) measure, by dynamically setting variables / options in the said measure and reusing it 100 times (I can post a sample of this if needed, but I guess you know what I'm talking about). The problem is another one: there isn't a "simple" way to assign (and "lock down") the values coming from that measure to the meter or meters where they are displayed (and say, for example, "I need meter 1 to display the 1st output of measure X, meter 2 to display the 2nd output of measure X, and so on"). In other words, the problem is not the execution of the job, but the distribution of the results / output. And yeah, in such cases, of course, Lua is pretty much the only simple enough answer.
User avatar
jsmorley
Developer
Posts: 21217
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Number of character or phrase included in a string

Post by jsmorley »

I guess my overriding point in all of this is that there is no explicit "benefit" in avoiding Lua at all costs. To be honest, I just wouldn't waste a moment of my time saying "Ok, that's easy in Lua, but there must be some way, no matter how complicated, to do the same thing without Lua".

The beauty of Lua, the thing I like about it so much, is that you just need one measure:

Code: Select all

[Lua]
Measure=Script
ScriptFile=MyLua.lua
Disabled=1
And that opens up the entirety of the power of Lua for you in your skins. Then you can write one or a hundred MyFunction() calls in MyLua.lua, and use them anywhere you need them, on demand. They use no resources at all until you "call" one of them, and they can be called, in a specific and reusable way, using Inline Lua, right where and when you need the "answer".

As you alluded to, a [MeasureName] in Rainmeter can only have one "value" at a time. Sure you can !SetOption it and !UpdateMeasure it to change the value, but it still can only have one value at a time. So it makes it a challenge indeed to "reuse" it in multiple places, with dynamic values that adjust to "where" and "when" it is used. Inline Lua addresses that issue. Maybe you don't need a [MeasureName] at all, but rather [&Lua:MyFunction(MyArgument)], where in a sense "MyArgument" plays the role that !SetOption does.

To me, Lua is just a part of Rainmeter, as much as IfCondition or Substitute, or any other option. Using that one measure above, all the libraries and procedural functionality Lua has are in effect added to the toolbelt you have for writing skins.

It's one of the simplest programming languages ever developed, and has a pretty low threshold for entry, even by someone who isn't a programmer.

Now mind you, I'm not going to use Lua when there is a perfectly good alternative in Rainmeter that doesn't require me to include an external .lua file with my skin. All things being equal, I'd just as soon not. But if I can do something easier or more efficiently in Lua, I'm not going to hesitate. Writing Lua code is a lot of fun for me.