Shorten MQTT topic filtering function - scala

I wrote the following logic function, but I am sure it is possible to write it (way) shorter.
In case you are unfamiliar with MQTT wildcards, you can read up on them here.
self is the topic we are "subscribed" to, containing zero or more wildcards. incoming is the topic we received something on, which must match the self topic either fully, or conforming to the wildcard rules.
All my tests on this function succeed, but I just don't like the lengthiness and "iffyness" of this Scala function.
def filterTopic(incoming: String, self: String): Boolean = {
if (incoming == self || self == "#") {
true
} else if (self.startsWith("#") || (self.contains("#") && !self.endsWith("#")) || self.endsWith("+")) {
false
} else {
var valid = true
val selfSplit = self.split('/')
var j = 0
for (i <- selfSplit.indices) {
if (selfSplit(i) != "+" && selfSplit(i) != "#" && selfSplit(i) != incoming.split('/')(i)) {
valid = false
}
j += 1
}
if (j < selfSplit.length && selfSplit(j) == "#") {
j += 1
}
j == selfSplit.length && valid
}
}

Here's a shot at it assuming that '+' can be at the end and that the topics are otherwise well-structured
def filterTopic(incoming: String, self: String): Boolean = {
// helper function that works on lists of parts of the topics
def go(incParts: List[String], sParts: List[String]): Boolean = (incParts, sParts) match {
// if they're equivalent lists, the topics match
case (is, ss) if is == ss => true
// if sParts is just a single "#", the topics match
case (_, "#" :: Nil) => true
// if sParts starts with '+', just check if the rest match
case (_ :: is, s :: ss) if s == "+" =>
go(is, ss)
// otherwise the first parts have to match, and we check the rest
case (i :: is, s :: ss) if i == s =>
go(is, ss)
// otherwise they don't match
case _ => false
}
// split the topic strings into parts
go(incoming.split('/').toList, self.split('/').toList)
}

Related

Using a predicate to search through a string - Scala

I'm having difficulty figuring out how to search through a string with a given predicate and determining its position in the string.
def find(x: Char => Boolean): Boolean = {
}
Example, if x is (_ == ' ')
String = "hi my name is"
It would add 2 to the counter and return true
I'm guessing that this is what you want...
Since find is a higher-order function (HOF) - that is, it's a function that takes a function as an argument - it likely needs to be applied to a String instance. The predicate (the function argument to find) determines when the character you're looking for is found, and the find method reports the position at which the character was found. So find should return an Option[Int], not a Boolean, that way you don't lose the information about where the character was found. Note that you can still change an Option[Int] result to a Boolean value (with true indicating the search was successful, false not) by applying .isDefined to the result.
Note that I've renamed find to myFind to avoid a clash with the built-in String.find method (which does a similar job).
import scala.annotation.tailrec
// Implicit class cannot be a top-level element, so it's put in an object.
object StringUtils {
// "Decorate" strings with additional functions.
final implicit class MyRichString(val s: String)
extends AnyVal {
// Find a character satisfying predicate p, report position.
def myFind(p: Char => Boolean): Option[Int] = {
// Helper function to keep track of current position.
#tailrec
def currentPos(pos: Int): Option[Int] = {
// If we've passed the end of the string, return None. Didn't find a
// character satisfying predicate.
if(pos >= s.length) None
// Otherwise, if the predicate passes for the current character,
// return position wrapped in Some.
else if(p(s(pos))) Some(pos)
// Otherwise, perform another iteration, looking at the next character.
else currentPos(pos + 1)
}
// Start by looking at the first (0th) character.
currentPos(0)
}
}
}
import StringUtils._
val myString = "hi my name is"
myString.myFind(_ == ' ') // Should report Some(2)
myString.myFind(_ == ' ').isDefined // Should report true
myString.myFind(_ == 'X') // Should report None
myString.myFind(_ == 'X').isDefined // Should report false
If the use of an implicit class is a little too much effort, you could implement this as a single function that takes the String as an argument:
def find(s: String, p: Char => Boolean): Option[Int] = {
// Helper function to keep track of current position.
#tailrec
def currentPos(pos: Int): Option[Int] = {
// If we've passed the end of the string, return None. Didn't find a
// character satisfying predicate.
if(pos >= s.length) None
// Otherwise, if the predicate passes for the current character,
// return position wrapped in Some.
else if(p(s(pos))) Some(pos)
// Otherwise, perform another iteration, looking at the next character.
else currentPos(pos + 1)
}
// Start by looking at the first (0th) character.
currentPos(0)
}
val myString = "hi my name is"
find(myString, _ == ' ') // Should report Some(2)
find(myString, _ == ' ').isDefined // Should report true
find(myString, _ == 'X') // Should report None
find(myString, _ == 'X').isDefined // Should report false
Counter:
"hi my name is".count (_ == 'm')
"hi my name is".toList.filter (_ == 'i').size
Boolean:
"hi my name is".toList.exists (_ == 'i')
"hi my name is".contains ('j')
Position(s):
"hi my name is".zipWithIndex.filter {case (a, b) => a == 'i'}
res8: scala.collection.immutable.IndexedSeq[(Char, Int)] = Vector((i,1), (i,11))
Usage of find:
scala> "hi my name is".find (_ == 'x')
res27: Option[Char] = None
scala> "hi my name is".find (_ == 's')
res28: Option[Char] = Some(s)
I would suggest separating the character search and position into individual methods, each of which leverages built-in functions in String, and wrap in an implicit class:
object MyStringOps {
implicit class CharInString(s: String) {
def charPos(c: Char): Int = s.indexOf(c)
def charFind(p: Char => Boolean): Boolean =
s.find(p) match {
case Some(_) => true
case None => false
}
}
}
import MyStringOps._
"hi my name is".charPos(' ')
// res1: Int = 2
"hi my name is".charFind(_ == ' ')
// res2: Boolean = true

Scala: Make sure braces are balanced

I am running a code to balance brackets in statement. I think i have gotten it correct but it is failing on one particular statement, i need to understand why?
This is the test in particular it is failing "())("
More than the coding i think i need to fix the algo, any pointers?
def balance(chars: List[Char]): Boolean = {
def find(c: Char, l: List[Char], i: Int): Int={
if( l.isEmpty ) {
if(c=='(')
i+1
else if(c==')')
i-1
else
i
}
else if (c=='(')
find(l.head, l.tail, i+1)
else if(c==')')
find(l.head,l.tail, i-1)
else
find(l.head,l.tail, i)
}
if(find(chars.head, chars.tail,0) ==0 )
true
else
false
}
balance("())(".toList) //passes when it should fail
balance(":-)".toList)
balance("(if (zero? x) max (/ 1 x))".toList)
balance("I told him (that it's not (yet) done).\n(But he wasn't listening)".toList)
Here is a version:
def balance(chars: List[Char]): Boolean = {
def inner(c: List[Char], count: Int): Boolean = c match {
case Nil => count == 0 // Line 1
case ')' :: _ if count < 1 => false // Line 2
case ')' :: xs => inner(xs, count - 1) // Line 3
case '(' :: xs => inner(xs, count + 1) // Line 4
case _ :: xs => inner(xs, count) // Line 5
}
inner(chars, 0)
}
So in your code, I think you are missing the additional check for count < 1 when you encounter the right paranthesis! So you need an additional else if that checks for both the ')' and count < 1 (Line 2 in the example code above)
Using map:
def balance(chars: List[Char]): Boolean = {
chars.map(c =>
c match {
case '(' => 1
case ')' => -1
case _ => 0
}
).scanLeft(0)(_ + _).dropWhile(_ >= 0).isEmpty
}
You've made a very simple and completely understandable mistake. The parentheses in )( are balanced, by your current definition. It's just that they're not balanced in the way we would usually think. After the first character, you have -1 unclosed parentheses, and then after the second characte we're back to 0, so everything is fine. If the parenthesis count ever drops below zero, the parentheses cannot possibly be balanced.
Now there are two real ways to handle this. The quick and dirty solution is to throw an exception.
case object UnbalancedException extends Exception
if (i < 0)
throw UnbalancedException
then catch it and return false in balance.
try {
... // find() call goes in here
} catch {
case UnbalancedException => false
}
The more functional solution would be to have find return an Option[Int]. During the recursion, if you ever get a None result, then return None. Otherwise, behave as normally and return Some(n). If you ever encounter the case where i < 0 then return None to indicate failure. Then in balance, if the result is nonzero or the result is None, return false. This can be made prettier with for notation, but if you're just starting out then it can be very helpful to write it out by hand.
You can also use the property of Stack data structure to solve this problem. When you see open bracket, you push it into the stack. When you see close bracket, you pop from the stack (instead of Stack I'm using List, because immutable Stack is deprecated in Scala):
def isBalanced(chars: Seq[Char]): Boolean = {
import scala.annotation.tailrec
case class BracketInfo(c: Char, idx: Int)
def isOpen(c: Char): Boolean = c == '('
def isClose(c: Char): Boolean = c == ')'
def safePop[T](stack: List[T]): Option[T] = {
if (stack.length <= 1) stack.headOption
else stack.tail.headOption
}
#tailrec
def isBalanced(chars: Seq[Char], idx: Int, stack: List[BracketInfo]): Boolean = {
chars match {
case Seq(c, tail#_*) =>
val newStack = BracketInfo(c, idx) :: stack // Stack.push
if (isOpen(c)) isBalanced(tail, idx + 1, newStack)
else if (isClose(c)) {
safePop(stack) match {
case Some(b) => isBalanced(tail, idx + 1, stack.tail)
case None =>
println(s"Closed bracket '$c' at index $idx was not opened")
false
}
}
else isBalanced(tail, idx + 1, stack)
case Seq() =>
if (stack.nonEmpty) {
println("Stack is not empty => there are non-closed brackets at positions: ")
println(s"${stack.map(_.idx).mkString(" ")}")
}
stack.isEmpty
}
}
isBalanced(chars, 0, List.empty[BracketInfo])
}

Iterate through scala Vector and pair a certain object with the next one

Assuming that I have a collection (Vector[Int]),1,2,5,4,3,5,5,5,6,7,7 and want to get another collection (Vector[Vector[Int]]) pairing every number 5 with the next number (1),(2),(5,4),(3),(5,5),(5,6),(7),(7) what are my options apart from this:
var input= Vector.[Int]
var output = Vector.empty[Vector[Int]]
var skip = false
for(i <- input.indices){
if (input(i) == 5 && skip == false){
output = output :+ input(i) :+ input(i + 1)
skip = true;
}else if(input(i - 1) != 5){
output = output :+ input(i)
}else{
skip = false;
}
}
which works but is not very scala-like.
Would it be possible to achieve the same result with a for comprehension? for(x <- c; if cond) yield {...}
You can use foldLeft
val output = input.foldLeft (Vector.empty[Vector[Int]]) { (result, next) =>
if(!result.isEmpty && result.last == Vector(5)) {
result.dropRight(1) :+ Vector(5, next)
} else {
result :+ Vector(next)
}
}
You could use pattern matching as well
def prepareVector(lv: Vector[Int]): Vector[Vector[Int]] = {
val mv = new ArrayBuffer[Vector[Int]]()
def go(ll: List[Int]): Unit = ll match {
case y :: Nil => mv += Vector(y)
case 5 :: ys => {
mv += Vector(5, ys.head)
go(ys.tail)
}
case y :: ys => {
mv += Vector(y)
go(ys)
}
case Nil => None
}
go(lv.toList)
mv.toVector
}

Scala Do While Loop Not Ending

I'm new to scala and i'm trying to implement a do while loop but I cannot seem to get it to stop. Im not sure what i'm doing wrong. If someone could help me out that would be great. Its not the best loop I know that but I am new to the language.
Here is my code below:
def mnuQuestionLast(f: (String) => (String, Int)) ={
var dataInput = true
do {
print("Enter 1 to Add Another 0 to Exit > ")
var dataInput1 = f(readLine)
if (dataInput1 == 0){
dataInput == false
} else {
println{"Do the work"}
}
} while(dataInput == true)
}
You're comparing a tuple type (Tuple2[String, Int] in this case) to 0, which works because == is defined on AnyRef, but doesn't make much sense when you think about it. You should be looking at the second element of the tuple:
if (dataInput1._2 == 0)
Or if you want to enhance readability a bit, you can deconstruct the tuple:
val (line, num) = f(readLine)
if (num == 0)
Also, you're comparing dataInput with false (dataInput == false) instead of assigning false:
dataInput = false
Your code did not pass the functional conventions.
The value that the f returns is a tuple and you should check it's second value of your tuple by dataInput1._2==0
so you should change your if to if(dataInput1._2==0)
You can reconstruct your code in a better way:
import util.control.Breaks._
def mnuQuestionLast(f: (String) => (String, Int)) = {
breakable {
while (true) {
print("Enter 1 to Add Another 0 to Exit > ")
f(readLine) match {
case (_, 0) => break()
case (_,1) => println( the work"
case _ => throw new IllegalArgumentException
}
}
}
}

I want to pattern match from Array of String with a single String in scala?

val aggFilters = Array["IR*","IR_"]
val aggCodeVal = "IR_CS_BPV"
val flag = compareFilters(aggFilters,aggCodeVal)
As per my requirement I want to compare the patterns given in the aggFilters with aggCodeVal. The first pattern "IR*" is a match with "IR_CS_BPV" but not the second one, hence I want to break out of the for loop after the match is found so that I don't go for the second one "IR_". I don't want to use break statement like java.
def compareFilters(aggFilters: Array[String], aggCodeVal: String): Boolean = {
var flag: Boolean = false
for (aggFilter <- aggFilters) {
if (aggFilter.endsWith("*")
&& aggCodeVal.startsWith(aggFilter.substring(0, aggFilter.length() - 1))) {
flag = true
}
else if (aggFilter.startsWith("*")
&& aggCodeVal.startsWith(aggFilter.substring(1, aggFilter.length()))) {
flag = true
}
else if (((aggFilter startsWith "*")
&& aggFilter.endsWith("*"))
&& aggCodeVal.startsWith(aggFilter.substring(1, aggFilter.length() - 1))) {
flag = true
}
else if (aggFilter.equals(aggCodeVal)) {
flag = true
}
else {
flag = false
}
}
flag
}
If * is your only wild-card character, you should be able to leverage Regex to do your match testing.
def compareFilters(aggFilters: Array[String], aggCodeVal: String): Boolean =
aggFilters.exists(f => s"$f$$".replace("*",".*").r.findAllIn(aggCodeVal).hasNext)
You can use the built-in exists method to do it for you.
Extract a function that compares a single filter, with this signature:
def compareFilter(aggFilter: String, aggCodeVal: String): Boolean
And then:
def compareFilters(aggFilters: Array[String], aggCodeVal: String): Boolean = {
aggFilters.exists(filter => compareFilter(filter, aggCodeVal))
}
The implementation of compareFilter, BTW, can be shortened to something like:
def compareFilter(aggFilter: String, aggCodeVal: String): Boolean = {
(aggFilter.startsWith("*") && aggFilter.endsWith("*") && aggCodeVal.startsWith(aggFilter.drop(1).dropRight(1))) ||
(aggFilter.endsWith("*") && aggCodeVal.startsWith(aggFilter.dropRight(1))) ||
(aggFilter.startsWith("*") && aggCodeVal.startsWith(aggFilter.drop(1))) ||
aggFilter.equals(aggCodeVal)
}
But - double check me on that one, not sure I followed your logic perfectly.