Getting line numbers for bools in ruamel.yaml - ruamel.yaml

I followed your advice in this post to get line numbers for strings. Worked like a charm. It was then suggested a similar pattern can be used to get line numbers for bools and ints. So I tried a few things and finally got it to work for bools, but I had to edit 1 line in the ruamel.yaml source code to get it to work. My question is, is this the proper way to implement line numbers for bools? Or is there a way to do it without having to modifying the ruamel source code?
Here is the one line in ruamel I had to change in constructor.py: Do not do the conversion/lookup back to a native bool, but instead return the overloaded ScalarBoolean which holds the line number:
def construct_yaml_bool(self, node):
# type: (Any) -> bool
value = self.construct_scalar(node)
return value # add this
# return self.bool_values[value.lower()]. # remove this
And the implementation I did to overload the constructor and process booleans (I can probably get rid of the lower() class method now that I'm not calling lower() from the ruamel source):
import ruamel.yaml
class Boolean(ruamel.yaml.scalarbool.ScalarBoolean):
def __init__(self, value): # constructor
self.__slots__ = ["lc", "bool_values", "internal_value"]
self.bool_values = []
if value is True:
self.bool_values.append("true")
self.internal_value = True
else:
self.bool_values.append("false")
self.internal_value = False
def lower(self):
if self.internal_value is True:
return "true"
else:
return "false"
class BoolStr(ruamel.yaml.scalarstring.ScalarString):
__slots__ = "lc"
style = ""
def __new__(cls, value):
return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value)
# def __init__(self, value): # constructor
# self.__slots__ = "lc"
# self.bool_values = []
# if value is True:
# # self.bool_values.append("true")
# self.internal_value = True
# else:
# # self.bool_values.append("false")
# self.internal_value = False
# def lower(self):
# if self.internal_value is True:
# return "true"
# else:
# return "false"
class Str(ruamel.yaml.scalarstring.ScalarString):
__slots__ = "lc"
style = ""
def __new__(cls, value):
return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value)
class MyPreservedScalarString(ruamel.yaml.scalarstring.PreservedScalarString):
__slots__ = "lc"
class MyDoubleQuotedScalarString(ruamel.yaml.scalarstring.DoubleQuotedScalarString):
__slots__ = "lc"
class MySingleQuotedScalarString(ruamel.yaml.scalarstring.SingleQuotedScalarString):
__slots__ = "lc"
class MyConstructor(ruamel.yaml.constructor.RoundTripConstructor):
def construct_scalar(self, node):
# type: (Any) -> Any
if not isinstance(node, ruamel.yaml.nodes.ScalarNode):
raise ruamel.yaml.constructor.ConstructorError(
None, None, "expected a scalar node, but found %s" % node.id, node.start_mark
)
if node.style == "|" and isinstance(node.value, ruamel.yaml.compat.text_type):
ret_val = MyPreservedScalarString(node.value)
elif bool(self._preserve_quotes) and isinstance(node.value, ruamel.yaml.compat.text_type):
if node.style == "'":
ret_val = MySingleQuotedScalarString(node.value)
elif node.style == '"':
ret_val = MyDoubleQuotedScalarString(node.value)
elif node.style is None and (node.value == "true" or node.value == "false"):
if node.value == "true":
# ret_val = BoolStr(node.value)
ret_val = Boolean(True)
else:
# ret_val = BoolStr(node.value)
ret_val = Boolean(False)
else:
ret_val = Str(node.value)
else:
ret_val = Str(node.value)
ret_val.lc = ruamel.yaml.comments.LineCol()
ret_val.lc.line = node.start_mark.line + 1
ret_val.lc.col = node.start_mark.column
return ret_val
Any advice is greatly appreciated.
Edit:
I de-monkey-patched your code and put the overload in MyConstructor, but contruct_yaml_bool never gets called inside of MyConstructor. construct_scalar gets called, but never construct_yaml_bool.
Here is the code I use to setup ruamel.yaml:
yaml = YAML()
yaml.preserve_quotes = True
yaml.Constructor = yaml_parser_classes.MyConstructor
with open(full_path_to_file) as fp:
yaml_data = yaml.load(fp)
And here is my full file to overload the round trip constructor:
import ruamel.yaml
class Boolean(ruamel.yaml.scalarbool.ScalarBoolean):
def __init__(self, value): # constructor
self.__slots__ = ["lc", "bool_values", "internal_value"]
self.bool_values = []
if value is True:
self.bool_values.append("true")
self.internal_value = True
else:
self.bool_values.append("false")
self.internal_value = False
class BoolStr(ruamel.yaml.scalarstring.ScalarString):
__slots__ = "lc"
style = ""
def __new__(cls, value):
return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value)
class Str(ruamel.yaml.scalarstring.ScalarString):
__slots__ = "lc"
style = ""
def __new__(cls, value):
return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value)
class MyPreservedScalarString(ruamel.yaml.scalarstring.PreservedScalarString):
__slots__ = "lc"
class MyDoubleQuotedScalarString(ruamel.yaml.scalarstring.DoubleQuotedScalarString):
__slots__ = "lc"
class MySingleQuotedScalarString(ruamel.yaml.scalarstring.SingleQuotedScalarString):
__slots__ = "lc"
class MyConstructor(ruamel.yaml.constructor.RoundTripConstructor):
def construct_scalar(self, node):
# type: (Any) -> Any
if not isinstance(node, ruamel.yaml.nodes.ScalarNode):
raise ruamel.yaml.constructor.ConstructorError(
None, None, "expected a scalar node, but found %s" % node.id, node.start_mark
)
if node.style == "|" and isinstance(node.value, ruamel.yaml.compat.text_type):
ret_val = MyPreservedScalarString(node.value)
elif bool(self._preserve_quotes) and isinstance(node.value, ruamel.yaml.compat.text_type):
if node.style == "'":
ret_val = MySingleQuotedScalarString(node.value)
elif node.style == '"':
ret_val = MyDoubleQuotedScalarString(node.value)
elif node.style is None and (node.value == "true" or node.value == "false"):
if node.value == "true":
# ret_val = BoolStr(node.value)
ret_val = Boolean(True)
else:
# ret_val = BoolStr(node.value)
ret_val = Boolean(False)
else:
ret_val = Str(node.value)
else:
ret_val = Str(node.value)
ret_val.lc = ruamel.yaml.comments.LineCol()
ret_val.lc.line = node.start_mark.line + 1
ret_val.lc.col = node.start_mark.column
return ret_val
def construct_yaml_bool(self, node):
# type: (Any) -> bool
value = self.construct_scalar(node)
return value # Changed to this to get bool with line numbers working
# Replace below lookup of raw bool type with above
# return which returns our ScalarBoolean with line numbers
# return self.bool_values[value.lower()]

Related

Algorithms to check if string has unique characters in Scala. Why is O(N2) verison quicker?

I implemented two versions of the algorithm in Scala (without using sets, by the way). A first one :
def isContained(letter: Char, word: String): Boolean =
if (word == "") false
else if (letter == word.head) true
else isContained(letter, word.tail)
def hasUniqueChars(stringToCheck: String): Boolean =
if (stringToCheck == "") true
else if (isContained(stringToCheck.head, stringToCheck.tail)) false
else hasUniqueChars(stringToCheck.tail)
which is in O(N2).
And a second one :
def hasUniqueChars2Acc(str: String, asciiTable: List[Boolean]): Boolean = {
if (str.length == 0) true
else if (asciiTable(str.head.toByte)) false
else hasUniqueChars2Acc(str.tail, asciiTable.updated(str.head.toByte, true))
}
def hasUniqueChars2(str: String): Boolean = {
val virginAsciiTable = List.fill(128)(false)
if (str.length > 128) false
else hasUniqueChars2Acc(str, virginAsciiTable)
}
which is in O(N).
But when testing, the second version takes as much as 20 times the duration of the first one. Why? Is it related to the .updated method ?

How to extend binary with Scala?

I am trying to complete the below exercise:
I have attempted it below, but my code is not acting as expected.
def extend(p: Long): Long = {
var e = p.toBinaryString
if ( e.count(_== '1') % 2 == 0) {
e="0"+e
}else {
e="1"+e
}
e.toLong
}
What am I doing wrong here? I don't understand how I'm supposed to change binary right Hex.
#Test def testExtend() {
assertEquals("extend(0x0000000000000000L)", 0x0000000000000000L, extend(0x0000000000000000L))
assertEquals("extend(0x0000000000000001L)", 0x8000000000000001L, extend(0x0000000000000001L))
assertEquals("extend(0x0000000000000011L)", 0x0000000000000011L, extend(0x0000000000000011L))
assertEquals("extend(0x8000000000000000L)", 0x0000000000000000L, extend(0x8000000000000000L))
assertEquals("extend(0x8000000000F00000L)", 0x0000000000F00000L, extend(0x8000000000F00000L))
assertEquals("extend(0x0000001000300000L)", 0x8000001000300000L, extend(0x0000001000300000L))
}
The first problem is that .toLong assumes that what's being converted is the String representation of a decimal value. So "10" is assumed to represent ten (decimal), not two (binary).
The next problem is that Long has a fixed length. You can't add an extra bit to it. You have to flip an existing bit.
def extend(p: Long): Long =
if (p.toBinaryString.count(_ == '1') % 2 == 0) p
else p ^ Long.MinValue
testing:
0x0000000000000000L == extend(0x0000000000000000L) //res0: Boolean = true
0x8000000000000001L == extend(0x0000000000000001L) //res1: Boolean = true
0x0000000000000011L == extend(0x0000000000000011L) //res2: Boolean = true
0x0000000000000000L == extend(0x8000000000000000L) //res3: Boolean = true
0x0000000000F00000L == extend(0x8000000000F00000L) //res4: Boolean = true
0x8000001000300000L == extend(0x0000001000300000L) //res5: Boolean = true

How I reverse a Short in Scala?

def reverse(w: Short): Short = { w.toBinaryString.reverse.toShort }
This should be work like this:
reverse(0x0000.toShort)
0x0000.toShort
reverse(0xFFFF.toShort)
0xFFFF.toShort
reverse(0xAAAA.toShort)
0x5555.toShort
reverse(0x1234.toShort)
0x2C48.toShort
I'd forgo the String translations for a more mathematical approach.
def reverse(w :Short) :Short =
Stream.iterate(w.toInt, 16)(_ >> 1)
.foldLeft(0:Short){case (s,i) => ((s<<1)+(i&1)).toShort}
testing:
reverse(0) == 0 //res0: Boolean = true
reverse(-1) == -1 //res1: Boolean = true
reverse(0xAAAA.toShort) == 0x5555 //res2: Boolean = true
reverse(0x1234.toShort) == 0x2C48 //res3: Boolean = true

How to remove' identifier expected but integer literal found error' in Scala?

I am trying to build a function to identify if a an expresson has balanced parenthesis or not.
My problem is that I am getting this error on my functions for the variables opening_index and closing_index :
Error:(3, 30) identifier expected but integer literal found.
var opening_index: Int =-1
Error:(6, 30) identifier expected but integer literal found.
var closing_index: Int =-1
Error:(34, 30) identifier expected but integer literal found.
var opening_index: Int =-1
Error:(37, 30) identifier expected but integer literal found.
var closing_index: Int =-1
I have been googling my error for the last hour without finding any valuable cluses.
Here is my code:
object Test
{
def balance(chars: List[Char]): Boolean=
{
var opening_index: Int =-1
var closing_index: Int =-1
opening_index=chars.indexOf('(')
closing_index=chars.indexOf(')')
if (opening_index ==-1 && closing_index==-1)
{
true
}
if (closing_index>-1 && opening_index>-1)
{
if (closing_index<=opening_index) return(false)
else
{
balance(chars.drop(closing_index).drop(opening_index))
}
}
else
return (false)
}
}
The tokenizer doesn't know, where a token ends or starts. Use some delimiters, which makes the code more readable for humans too:
object Test {
def balance(chars: List[Char]): Boolean =
{
var opening_index: Int = -1
var closing_index: Int = -1
opening_index=chars.indexOf ('(')
closing_index=chars.indexOf (')')
if (opening_index == -1 && closing_index == -1)
{
true
}
if (closing_index > -1 && opening_index > -1)
{
if (closing_index <= opening_index)
return false
else
balance (chars.drop (closing_index).drop (opening_index))
}
else
return false
}
}
Since you only initialize your vars to reinitialize them directly again, you can easily earn some functional points by declaring them as vals:
def balance (chars: List[Char]): Boolean =
{
val opening_index: Int = chars.indexOf ('(')
val closing_index: Int = chars.indexOf (')')
For recursive calls, you don't have to bother - those generate a new pair of values which don't interfere with those of the calling context.
You might also note, that return statements dont need a function-call-look with parens return (false) but can be written as return false, and even the return keyword is most of the time superflous.
object Test {
def balance (chars: List[Char]): Boolean =
{
val opening_index: Int = chars.indexOf ('(')
val closing_index: Int = chars.indexOf (')')
if (opening_index == -1 && closing_index == -1)
{
true // but note the else in the 2nd next line
}
else if (closing_index > -1 && opening_index > -1)
{
if (closing_index <= opening_index)
false
else
balance (chars.drop (closing_index).drop (opening_index))
}
else
false
}
}
When Scala sees =-1, it gets tokenized (split) not as = -1 (as in other languages you may be familiar with), but as =- 1, since =- is a perfectly fine identifier in Scala.
Since = hasn't been seen, the entire Int =- 1 must be a type. By infix notation for types, it's equivalent to =-[Int, 1]. Except 1 here must be a type. To be a type it should start with an identifier, and that's what the error message is saying. If you fixed that, it would complain about unknown =- type next.
This can be simplified by just counting how many of each there are and seeing if the numbers match up:
def balance (chars: List[Char]): Boolean = {
val opening = chars.count(_ == '(')
val closing = chars.count(_ == ')')
opening == closing
}
balance(List('(', ')', '(', ')', '(')) //false
balance(List('(', ')', '(', ')')) //true
If you want the balanced parentheses to be in order, you can do it recursively:
def balance(chars: List[Char], open: Int = 0): Boolean = {
if (chars.isEmpty)
open == 0
else if (chars.head == '(')
balance(chars.tail, open + 1)
else if (chars.head == ')' && open > 0)
balance(chars.tail, open - 1)
else
balance(chars.tail, open)
}
val x = "(apple + pear + (banana)))".toList
balance(x) //true
val y = "(()(".toList
balance(y) //false
This goes through every element of the list and adds/subtracts to/from a variable called open until the list is empty, at which point if open is 0 it returns true otherwise it returns false.

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.