Swift: math operation on regex capture numbers - regex

I like to change "I run 12 miles" to "I run 24 miles" using regex. "12" can be any number. I use the function "replace2()" to do the math operation. The capture parameter "$1" in passed to function replace2("$1") is fine. Please help!
func replace( str:String, pattern : String, repl:String)->String?{
let regex = NSRegularExpression .regularExpressionWithPattern(pattern, options: nil,error: nil)
let replacedString = regex.stringByReplacingMatchesInString( str, options: nil,
range: NSMakeRange(0, countElements( str)), withTemplate: repl )
return replacedString
}
replace( "I run 12 miles and walk 12.45 km","\\d+(.\\d+)?", "-")!
func replace2(x: String)->String {
let xx = x.toInt()! * 2 // error : return nil?
return String(format: "\(xx)")
}
replace( "I run 12 miles ","(\\d+)", replace2("$1") )! // error

Try this in a playground:
let str1: NSString = "I run 12 miles"
let str2 = "I run 12 miles"
let match = str1.rangeOfString("\\d+", options: .RegularExpressionSearch)
let finalStr = str1.substringWithRange(match).toInt()
let n: Double = 2.2*Double(finalStr!)
let newStr = str2.stringByReplacingOccurrencesOfString("\\d+", withString: "\(n)", options: NSStringCompareOptions.RegularExpressionSearch, range: nil)
println(newStr) //I run 26.4 miles
//or more simply
let newStr2 = "I run \(n) kilometers"
//yes, I know my conversion is off

If you want to dynamically catch the values and replace them based upon the captured value, you have to use enumerateMatchesInString. You can then then write a loop that does the replacements:
func stringWithDoubleNumbers(string: String) -> String {
// build array of ranges that need replacing
var ranges = [NSRange]()
let regex = NSRegularExpression(pattern: "[\\d.]+", options: nil, error: nil) as NSRegularExpression
regex.enumerateMatchesInString(string, options: nil, range: NSMakeRange(0, countElements(string))) {
match, flags, stop in
ranges.append(match.range)
}
var doubledString = NSMutableString(string: string)
// iterate backwards so that location is valid despite other replacements
for range in reverse(ranges) {
let foundString = doubledString.substringWithRange(range)
if let value = foundString.toInt() {
let numericString = "\(value * 2)"
doubledString.replaceCharactersInRange(range, withString: numericString)
} else {
let value = NSString(string: foundString).doubleValue
let numericString = "\(value * 2.0)"
doubledString.replaceCharactersInRange(range, withString: numericString)
}
}
return doubledString
}
I do some additional logic to handle integer values differently than floating point types, but you could simplify this if you don't want to worry about that complication.

// I experiment with Steve and Rob codes above, I think I can get the codes to solve the problem for simple string. I still get problem with complex string. I try to parse a data record with various multiple (-20) numbers with either integers/doubles not in any sequence like handling both str2 and str3 in a single routine. Anyway thanks to Steve and Rob.
let str2 = "I run 89 miles and 28.576 km"
let str3 = "I run 89.45 miles and 34 km in 34F degree"
var re = NSRegularExpression(pattern: "(\\d+).* (\\d+\\.\\d+)", options: nil, error: nil)
var match = re.firstMatchInString(str2, options: nil, range: NSMakeRange(0, countElements(str2)))
var startIndex = advance(str2.startIndex, match.rangeAtIndex(1).location)
var endIndex = advance(str2.startIndex, match.rangeAtIndex(1).location + match.rangeAtIndex(1).length)
let x1 = str2.substringWithRange(Range(start: startIndex, end: endIndex)).toInt()!
x1
startIndex = advance(str2.startIndex, match.rangeAtIndex(2).location)
endIndex = advance(str2.startIndex, match.rangeAtIndex(2).location + match.rangeAtIndex(2).length)
let x2 = str2.substringWithRange(Range(start: startIndex, end: endIndex))
let x2x = NSString(string: x2).doubleValue
let n1 = x1 * 2
let n2: Double = 2.0 * x2x
str2.stringByReplacingOccurrencesOfString("(\\d+)(.* )(\\d+\\.\\d+)", withString: "\(n1)$2\(n2)", options: NSStringCompareOptions.RegularExpressionSearch, range: nil)
// I run 178 miles and 57.152 km"

Related

Define a closure variable in buckle script

I'm trying to convert the following ES6 script to bucklescript and I cannot for the life of me figure out how to create a "closure" in bucklescript
import {Socket, Presence} from "phoenix"
let socket = new Socket("/socket", {
params: {user_id: window.location.search.split("=")[1]}
})
let channel = socket.channel("room:lobby", {})
let presence = new Presence(channel)
function renderOnlineUsers(presence) {
let response = ""
presence.list((id, {metas: [first, ...rest]}) => {
let count = rest.length + 1
response += `<br>${id} (count: ${count})</br>`
})
document.querySelector("main[role=main]").innerHTML = response
}
socket.connect()
presence.onSync(() => renderOnlineUsers(presence))
channel.join()
the part I cant figure out specifically is let response = "" (or var in this case as bucklescript always uses vars):
function renderOnlineUsers(presence) {
let response = ""
presence.list((id, {metas: [first, ...rest]}) => {
let count = rest.length + 1
response += `<br>${id} (count: ${count})</br>`
})
document.querySelector("main[role=main]").innerHTML = response
}
the closest I've gotten so far excludes the result declaration
...
...
let onPresenceSync ev =
let result = "" in
let listFunc = [%raw begin
{|
(id, {metas: [first, ...rest]}) => {
let count = rest.length + 1
result += `${id} (count: ${count})\n`
}
|}
end
] in
let _ =
presence |. listPresence (listFunc) in
[%raw {| console.log(result) |} ]
...
...
compiles to:
function onPresenceSync(ev) {
var listFunc = (
(id, {metas: [first, ...rest]}) => {
let count = rest.length + 1
result += `${id} (count: ${count})\n`
}
);
presence.list(listFunc);
return ( console.log(result) );
}
result is removed as an optimization beacuse it is considered unused. It is generally not a good idea to use raw code that depends on code generated by BuckleScript, as there's quite a few surprises you can encounter in the generated code.
It is also not a great idea to mutate variables considered immutable by the compiler, as it will perform optimizations based on the assumption that the value will never change.
The simplest fix here is to just replace [%raw {| console.log(result) |} ] with Js.log result, but it might be enlightening to see how listFunc could be written in OCaml:
let onPresenceSync ev =
let result = ref "" in
let listFunc = fun [#bs] id item ->
let count = Js.Array.length item##meta in
result := {j|$id (count: $count)\n|j}
in
let _ = presence |. (listPresence listFunc) in
Js.log !result
Note that result is now a ref cell, which is how you specify a mutable variable in OCaml. ref cells are updated using := and the value it contains is retrieved using !. Note also the [#bs] annotation used to specify an uncurried function needed on functions passed to external higher-order functions. And the string interpolation syntax used: {j| ... |j}

Google Sheets Search and Sum in two lists

I have a Google Sheets question I was hoping someone could help with.
I have a list of about 200 keywords which looks like the ones below:
**List 1**
Italy City trip
Italy Roundtrip
Italy Holiday
Hungary City trip
Czechia City trip
Croatia Montenegro Roundtrip
....
....
And I then have another list with jumbled keywords with around 1 million rows. The keywords in this list don't exactly match with the first list. What I need to do is search for the keywords in list 1 (above) in list 2 (below) and sum all corresponding cost values. As you can see in the list below the keywords from list 1 are in the second list but with other keywords around them. For example, I need a formula that will search for "Italy City trip" from list 1, in list 2 and sum the cost when that keyword occurs. In this case, it would be 6 total. Adding the cost of "Italy City trip April" and "Italy City trip June" together.
**List 2** Cost
Italy City trip April 1
Italy City trip June 5
Next week Italy Roundtrip 4
Italy Holiday next week 1
Hungary City holiday trip 9
....
....
I hope that makes sense.
Any help would be greatly appreciated
try:
=ARRAYFORMULA(QUERY({IFNA(REGEXEXTRACT(PROPER(C1:C),
TEXTJOIN("|", 1, SORT(PROPER(A1:A), 1, 0)))), D1:D},
"select Col1,sum(Col2)
where Col1 is not null
group by Col1
label sum(Col2)''", 0))
You want to establish whether keywords in one list (List#1) can be found in another list (List#2).
List#2 is 1,000,000 rows long, so I would recommend segmenting the list so that execution times are not exceeded. That's something you will be able to establish by trial and error.
The solution is to use the javascript method indexOf.
Paraphrasing from w3schools: indexOf() returns the position of the first occurrence of a specified value in a string. If the value is not found, it returns -1. So testing if (idx !=-1){ will only return List#1 values that were found in List#2. Note: The indexOf() method is case sensitive.
function so5864274503() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var srcname = "source";
var tgtname = "target";
var sourceSheet = ss.getSheetByName(srcname);
var targetSheet = ss.getSheetByName(tgtname);
// get the source list
var sourceLR = sourceSheet.getLastRow();
var srcData = sourceSheet.getRange(1,1,sourceLR).getValues();
//get the target list
var targetLR = targetSheet.getLastRow();
var tgtlist = targetSheet.getRange(1,1,targetLR,2).getValues();
var totalcostvalues = [];
// start looping through the keywords (list 1)
for (var s = 0;s<srcData.length;s++){
var totalcost = 0;
var value = srcData[s][0]
// start looping through the strings (List 2)
for (var i=0;i<tgtlist.length;i++){
// set cost to zero
var cumcost = 0;
// use indexOf to test if keyword is in the string
var idx = tgtlist[i][0].indexOf(value);
// value of -1 = no match, value >-1 indicates posuton in the string where the key word was found
if (idx !=-1){
var cost = tgtlist[i][1]
cumcost = cumcost + cost;
totalcost = totalcost+cost
}
}//end of loop - list2
//Logger.log("DEBUG: Summary: "+value+", totalcost = "+totalcost)
totalcostvalues.push([totalcost])
}// end of loop - list1
//Logger.log(totalcostvalues); //DEBUG
sourceSheet.getRange(1,2,sourceLR).setValues(totalcostvalues);
}
I also got this one, but it's case sensitive a bit
function myFunction() {
var ss = SpreadsheetApp.getActive();
var sheet1 = ss.getSheets()[0];
var sheet2 = ss.getSheets()[1];
var valuesSheet1 = sheet1.getRange(2,1, (sheet1.getLastRow()-1), sheet1.getLastColumn()).getValues();
var valuesCol1Sheet1 = valuesSheet1.map(function(r){return r[0]});
var valuesCol2Sheet1 = valuesSheet1.map(function(r){return r[1]});
Logger.log(valuesCol2Sheet1);
var valuesSheet2 = sheet2.getRange(2,1, (sheet2.getLastRow()-1)).getValues();
var valuesCol1Sheet2 = valuesSheet2.map(function(r){return r[0]});
for (var i = 0; i<= valuesCol1Sheet2.length-1; i++){
var price = 0;
valuesCol1Sheet1.forEach(function(elt,index){
var position = elt.toLowerCase().indexOf(valuesCol1Sheet2[i].toLowerCase());
if(position >-1){
price = price + valuesCol2Sheet1[index];
}
});
sheet2.getRange((i+2),2).setValue(price);
};
}

How to change only variable color in a print label

I want change a color only for variable in a tuple print like this
result.text = ("\(var1) candy " + " of: \(var2) blablabla")
Now, how can I change color of "var1" and "var2", from the inspector I set red color for the label, but it's impossible change color only for variable.
Thanks a lot
What you have to do is create an attributed string for each color (let's say blue and yellow):
let t : NSAttributedString = NSAttributedString(string: "Hi", attributes: [NSForegroundColorAttributeName : UIColor.blue])
let t2 : NSAttributedString = NSAttributedString(string: " There", attributes: [NSForegroundColorAttributeName : UIColor.yellow])
If you want to join them together:
let final = NSMutableAttributedString(attributedString: t)
final.append(t2)
UPDATE
so in your case, since you want to color two different sections of your resulting string, you'd want to use the NSRange approach. So a method like this:
func colorTheVariables(_ var1Value: String,_ var2Value: String) {
let middle = " candy of: ".characters.count
let value = "\(var1Value) candy of : \(var2Value)"
let text = NSMutableAttributedString(string: value)
let range1 = NSRange(location: 0, length: var1Value.characters.count)
let range2 = NSRange(location: range1.length + middle, length: var2Value.characters.count)
text.addAttribute(NSForegroundColorAttributeName, value: var1Color, range: range1)
text.addAttribute(NSForegroundColorAttributeName, value: var2Color, range: range2)
result.attributedText = text
}
Should give you the coloring you want for var1 and var 2. It's important you apply text to attributedText of the label, not to text. Printing result.text should give you the content of the label, but printing result.attributedText will give you the weird nsAttributed string print.

Golang SplitAfter for Regexp type

In golang strings.SplitAfter method split text after an special character into an slice, but I didn't find a way for Regexp type to split text after matches. Is there a way to do that?
Example :
var text string = "1.2.3.4.5.6.7.8.9"
res := strings.Split(text, ".")
fmt.Println(res) // print [1 2 3 4 5 6 7 8 9]
res = strings.SplitAfter(text, ".")
fmt.Println(res) // print [1. 2. 3. 4. 5. 6. 7. 8. 9]
first at all, your regex "." is wrong for splitAfter function. You want number followed by value "." so the regex is: "[1-9]".
The function you are looking might look like this:
func splitAfter(s string, re *regexp.Regexp) (r []string) {
re.ReplaceAllStringFunc(s, func(x string) string {
s = strings.Replace(s,x,"::"+x,-1)
return s
})
for _, x := range strings.Split(s,"::") {
if x != "" {
r = append(r, x)
}
}
return
}
Than:
fmt.Println(splitAfter("healthyRecordsMetric",regexp.MustCompile("[A-Z]")))
fmt.Println(splitAfter("healthyrecordsMETetric",regexp.MustCompile("[A-Z]")))
fmt.Println(splitAfter("HealthyHecord Hetrics",regexp.MustCompile("[A-Z]")))
fmt.Println(splitAfter("healthy records metric",regexp.MustCompile("[A-Z]")))
fmt.Println(splitAfter("1.2.3.4.5.6.7.8.9",regexp.MustCompile("[1-9]")))
[Healthy Records Metric]
[healthy Records Metric]
[healthyrecords M E Tetric]
[Healthy Hecord Hetrics]
[healthy records metric]
[1. 2. 3. 4. 5. 6. 7. 8. 9]
Good luck!
Regexp type itself does not have a method to do that exactly that but it's quite simple to write a function that implements what your asking based on Regexp functionality:
func SplitAfter(s string, re *regexp.Regexp) []string {
var (
r []string
p int
)
is := re.FindAllStringIndex(s, -1)
if is == nil {
return append(r, s)
}
for _, i := range is {
r = append(r, s[p:i[1]])
p = i[1]
}
return append(r, s[p:])
}
Here I left a program to play with it.

Label displaying "Optional('#') in Swift when using NumberFormatter

I have the following code:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.decimal
numberFormatter.maximumFractionDigits = 2
let formattedNumber = numberFormatter.string(from: NSNumber(value: rawValue))
currentLogBF.text = "\(formattedNumber) BF"
In the above example, rawValue is a Double that is calculated when all of the input fields have values in them.
currentLogBF is a label in my View.
Whenever a calculation is completed, the label displays something like this:
Optional("12,307.01") BF
How do I get rid of the "Optional()" piece, so it just displays this:
12,307.01 BF
Any ideas what I am doing wrong here?
The function numberFormatter.string(from: NSNumber) will return you a String Optional (String?) instead of String.
You will need to unwrap it first like this
if let formattedNumber = numberFormatter.string(from: NSNumber(value: rawValue)) {
currentLogBF.text = "\(formattedNumber) BF"
} else {
Log.warn("Failed to format number!")
}
And as bonus, use String(format: "%# BF", formattedNumber) rather than "\(formattedNumber) BF" when dealing with optional.
String(format:) will give you compile error when you try to pass optional value as an argument
unwrapping an optional value
let formattedNumber:String? = numberFormatter.string(from: NSNumber(value: rawValue))
currentLogBF.text = "\(formattedNumber!) BF" //optional string. This will result in nil while unwrapping an optional value if value is not initialized or if initialized to nil.
currentLogBF.text = "\(formattedNumber) BF" //Optional("optional string") //nil values are handled in this statement