Kotlin Big Decimal division returning inaccurate results - android

I am building a calculator with Kotlin and android studio (my first project). All equations were working properly until recent tweaks with decimal format. Now large division operations returns inaccurate results.
For example 99,999,999 / 9% (0.09) = 1.11111110000E9
But 9% / 99,999,999 = 0 when it should = 9.0000000009E-10
Is this because the current DecimalFormat cannot return the negative exponent?
EDIT: after more testing I've found that changing division method to
doubleNum = doubleNum.divide(numBigDecimal, 10, BigDecimal.ROUND_HALF_UP).stripTrailingZeros()
running the same equation will return 9E-10 before formatting. After decimal format the result shows as "." only with no digits
// enum class and class properties
enum class LogicTypes {
None,Add,Subtract,Multiply,Divide
}
class MainActivity : AppCompatActivity() {
private var logicActive = false
private var currentLogic = LogicTypes.None
private var currentNum = BigDecimal.ZERO
private var displayNum = ""
private var memoryNum = BigDecimal.ZERO
// add num function - buttons 0-9 send indices to num arg
fun addNum(num: BigDecimal) {
val inputNum = num.toString()
if (displayNum == "0" && inputNum == "0") {
return
}
if (displayNum.contains(".")) {
val stringForm = displayNum.substring(displayNum.indexOf('.'), displayNum.length)
if (stringForm.length > 10) {
clearCalc()
Toast.makeText(this, "Only 10 digits after decimal point allowed.", Toast.LENGTH_SHORT).show()
return
}
}
if (displayNum.length >= 15 && !displayNum.contains(".")) {
clearCalc()
Toast.makeText(this, "Maximum of 15 digits allowed.", Toast.LENGTH_SHORT).show()
return
}
if (inputNum == "0" && currentNum.toDouble() == 0.0 && displayNum.contains(".")) {
if (displayNum.length > 11) {
clearCalc()
Toast.makeText(this, "Only 10 digits after decimal point allowed.", Toast.LENGTH_SHORT).show()
return
}
displayNum = "$displayNum$inputNum"
textView.text = displayNum
return
}
if (logicActive) {
logicActive = false
displayNum = "0"
}
displayNum = "$displayNum$inputNum"
updateDisplayNum()
}
// set currentNum and send to numFormat function to update textView
fun updateDisplayNum() {
if (currentNum.toString().length > 15) {
clearCalc()
Toast.makeText(this, "Maximum of 15 digits allowed.", Toast.LENGTH_SHORT).show()
return
}
val numBigDecimal = displayNum.toBigDecimal()
if(currentLogic == LogicTypes.None) {
if(displayNum.contains("-") && currentNum == BigDecimal.ZERO) {
textView.text = displayNum
return
} else {
currentNum = numBigDecimal
}
}
numFormat()
}
// format decimal and integers and update textview with exponent
fun numFormat() {
val numBigDecimal = displayNum.toBigDecimal()
if(displayNum.contains(".")) {
val stringForm = displayNum.substring(displayNum.indexOf('.'), displayNum.length)
var numFormat = "#,##0."
if(stringForm.length > 1) {
for (num in stringForm.indices-1) {
numFormat += "0"
}
}
if (displayNum.length > 16 || stringForm.length > 9) {
// stringform length > 9 works for division result - anything higher returns trailing zeros.
// anything under 11 for stringform condition results in inaccurate input -
// adding decimal through addNum() will return Exponential notation before logic
// I only want E notation on result only- have yet to test other equations -
// this can also make it impossible to take the result and conduct another logic operation as the result appends E0
// and thus the trailing digits after decimal is greater than 10
numFormat = "0.0000000000E0"
}
val df = DecimalFormat(numFormat)
textView.text = df.format(numBigDecimal)
return
}
var df = DecimalFormat("#,###")
if (displayNum.length > 15) {
df = DecimalFormat("0.0000000000E0")
}
textView.text = df.format(numBigDecimal)
}
// change logic to enum mode when button operator pressed
fun changeLogic(mode: LogicTypes) {
currentLogic = mode
logicActive = true
}
// calculate function
fun calculate() {
if (logicActive || currentLogic == LogicTypes.Divide && displayNum.toBigDecimal() == BigDecimal.ZERO
|| currentNum == BigDecimal.ZERO) {
Log.i(LOG_TAG, "caught the zero")
return
}
val numBigDecimal = displayNum.toBigDecimal()
var doubleNum = currentNum
val currentNumString = doubleNum.stripTrailingZeros().toPlainString()
val numBigDecimalString = numBigDecimal.stripTrailingZeros().toPlainString()
val addMsg = getString(R.string.calc_message, currentNumString, "+", numBigDecimalString)
val subMsg = getString(R.string.calc_message, currentNumString, "-", numBigDecimalString)
val multiMsg = getString(R.string.calc_message, currentNumString, "*", numBigDecimalString)
val divMsg = getString(R.string.calc_message, currentNumString, "/", numBigDecimalString)
when(currentLogic) {
LogicTypes.Add -> {
hintView.text = addMsg
doubleNum += numBigDecimal
doubleNum = doubleNum.stripTrailingZeros()
}
LogicTypes.Subtract -> {
hintView.text = subMsg
doubleNum -= numBigDecimal
doubleNum = doubleNum.stripTrailingZeros()
}
LogicTypes.Multiply -> {
hintView.text = multiMsg
doubleNum *= numBigDecimal
doubleNum = doubleNum.stripTrailingZeros()
}
LogicTypes.Divide -> {
hintView.text = divMsg
doubleNum /= numBigDecimal
doubleNum = doubleNum.stripTrailingZeros()
}
LogicTypes.None -> return
}
currentLogic = LogicTypes.None
displayNum = doubleNum.toString()
updateDisplayNum()
logicActive = true
}

Ok the issue was that I was using this in the calculate function.
displayNum = doubleNum.toString()
Changing to .toPlainString() gives correct notations. There are still issues with formatting but I'll see if I can work those out on my own
EDIT: I solved the formatting issue in the numFormat by creating a boolean property, setting it to true in the calculation function, and passing it to the numFormat condition:
if (displayNum.length > 16 || stringForm.length > 9 && resultActive) {
numFormat = "0.0000000000E0"
resultActive = false
}
This way the format only applies to calculated numbers
I also passed it to the addNum function for calculations made after the first calculation
if(resultActive) {
resultActive = false
}

Related

How do you remove extra zeros at the end of a decimal in Kotlin?

I'm creating a function that rounds large numbers over 1,000 and then returns a string of that rounded number. For example, "2374293" would return as "2.37m"
However, I dont want any extra zeros at the end of decimals like "25.00" or "100.50".
For Example:
What I want:
Input -> Output
"11000" -> "11k"
"11400" -> "11.4k"
What I get:
Input -> Output
"11000" -> "11.00k"
"11400" -> "11.40k"
How would I remove these zeros and decimal point(if it's a whole number) when needed?
Here is my code currently:
private fun roundBigNumb(numb: Long): String {
val newNumb = numb.toDouble()
return when {
numb in 1000..999994 -> {
BigDecimal(newNumb/1000).setScale(2, RoundingMode.HALF_EVEN).toString()+"k"
}
numb in 999995..999999 -> {
"999.99k"
}
numb in 1000000..999994999 -> {
BigDecimal(newNumb/1000000).setScale(2, RoundingMode.HALF_EVEN).toString()+"m"
}
numb in 999995000..999999999 -> {
"999.99m"
}
numb in 1000000000..999994999999 -> {
BigDecimal(newNumb/1000000000).setScale(2, RoundingMode.HALF_EVEN).toString()+"b"
}
numb in 999995000000..999999999999 -> {
"999.99b"
}
numb in 1000000000000..999994999999999 -> {
BigDecimal(newNumb/1000000000000).setScale(2, RoundingMode.HALF_EVEN).toString()+"t"
}
numb in 999995000000000..999999999999999 -> {
"999.99t"
}
numb >= 1000000000000000 -> "∞"
else -> numb.toString()
}
}
import java.math.BigDecimal
import java.math.RoundingMode.HALF_EVEN
fun roundBigNumber(number: Long): String {
fun calc(divisor: Long) = BigDecimal(number.toDouble() / divisor)
.setScale(2, HALF_EVEN)
.toString()
.dropLastWhile { it == '0' }
.dropLastWhile { it == '.' }
return when {
number in 1000..999994 -> calc(1000) + "k"
number in 999995..999999 -> "999.99k"
number in 1000000..999994999 -> calc(1000000) + "m"
number in 999995000..999999999 -> "999.99m"
number in 1000000000..999994999999 -> calc(1000000000) + "b"
number in 999995000000..999999999999 -> "999.99b"
number in 1000000000000..999994999999999 -> calc(1000000000000) + "t"
number in 999995000000000..999999999999999 -> "999.99t"
number >= 1000000000000000 -> "∞"
else -> number.toString()
}
}
Once you have converted the number to a string with up to 2 decimal places (as you are doing), you can use dropLastWhile to drop trailing zeros and decimal places.
Here is an example
fun prettyFormat(input: Double): String {
if( input == 0.0 ) return "0"
val prefix = if( input < 0 ) "-" else ""
val num = abs(input)
// figure out what group of suffixes we are in and scale the number
val pow = floor(log10(num)/3).roundToInt()
val base = num / 10.0.pow(pow * 3)
// Using consistent rounding behavior, always rounding down since you want
// 999999999 to show as 999.99M and not 1B
val roundedDown = floor(base*100)/100.0
// Convert the number to a string with up to 2 decimal places
var baseStr = BigDecimal(roundedDown).setScale(2, RoundingMode.HALF_EVEN).toString()
// Drop trailing zeros, then drop any trailing '.' if present
baseStr = baseStr.dropLastWhile { it == '0' }.dropLastWhile { it == '.' }
val suffixes = listOf("","k","M","B","T")
return when {
pow < suffixes.size -> "$prefix$baseStr${suffixes[pow]}"
else -> "${prefix}infty"
}
}
This produces
11411.0 = 11.41k
11000.0 = 11k
9.99996E8 = 999.99M
12.4 = 12.4
0.0 = 0
-11400.0 = -11.4k
If you don't care about zero or negative numbers it can be simplified a bit.

How to apply a mask date (mm/dd/yyyy) in TextField with Jetpack Compose?

I have a TextField in which there cannot be more than 10 characters, and the user is required to enter date in the format "mm/dd/yyyy". Whenever user types first 2 characters I append "/", when the user types next 2 characters I append "/" again.
I did the following to achieve this:
var maxCharDate = 10
TextField(
value = query2,
onValueChange = {
if (it.text.length <= maxCharDate) {
if (it.text.length == 2 || it.text.length == 5)
query2 = TextFieldValue(it.text + "/", selection = TextRange(it.text.length+1))
else
query2 = it
}
emailErrorVisible.value = false
},
label = {
Text(
"Date of Birth (mm/dd/yyyy)",
color = colorResource(id = R.color.bright_green),
fontFamily = FontFamily(Font(R.font.poppins_regular)),
fontSize = with(LocalDensity.current) { dimensionResource(id = R.dimen._12ssp).toSp() })
},
.
.
.
It's working except that the appended "/" doesn't get deleted on pressing backspace, while other characters do get deleted.
How do I make it such that "/" is deleted too on pressing backspace?
You can do something different using the onValueChange to define a max number of characters and using visualTransformation to display your favorite format without changing the value in TextField.
val maxChar = 8
TextField(
singleLine = true,
value = text,
onValueChange = {
if (it.length <= maxChar) text = it
},
visualTransformation = DateTransformation()
)
where:
class DateTransformation() : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return dateFilter(text)
}
}
fun dateFilter(text: AnnotatedString): TransformedText {
val trimmed = if (text.text.length >= 8) text.text.substring(0..7) else text.text
var out = ""
for (i in trimmed.indices) {
out += trimmed[i]
if (i % 2 == 1 && i < 4) out += "/"
}
val numberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
if (offset <= 1) return offset
if (offset <= 3) return offset +1
if (offset <= 8) return offset +2
return 10
}
override fun transformedToOriginal(offset: Int): Int {
if (offset <=2) return offset
if (offset <=5) return offset -1
if (offset <=10) return offset -2
return 8
}
}
return TransformedText(AnnotatedString(out), numberOffsetTranslator)
}
The / is being deleted but as soon as you delete, the length of the text becomes 2 or 5. So it checks the condition,
if (it.text.length == 2 || it.text.length == 5)
Since the condition is true now, the / appends again into the text. So it seems like it is not at all being deleted.
One way to solve this is by storing the previous text length and checking if the text length now is greater than the previous text length.
To achieve this, declare a variable below maxCharDate as
var previousTextLength = 0
And change the nested if condition to,
if ((it.text.length == 2 || it.text.length == 5) && it.text.length > previousTextLength)
And at last update the previousTextLength variable. Below the emailErrorVisible.value = false add
previousTextLength = it.text.length;
Implementation of VisualTranformation that accepts any type of mask for Jetpack Compose TextField:
class MaskVisualTransformation(private val mask: String) : VisualTransformation {
private val specialSymbolsIndices = mask.indices.filter { mask[it] != '#' }
override fun filter(text: AnnotatedString): TransformedText {
var out = ""
var maskIndex = 0
text.forEach { char ->
while (specialSymbolsIndices.contains(maskIndex)) {
out += mask[maskIndex]
maskIndex++
}
out += char
maskIndex++
}
return TransformedText(AnnotatedString(out), offsetTranslator())
}
private fun offsetTranslator() = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
val offsetValue = offset.absoluteValue
if (offsetValue == 0) return 0
var numberOfHashtags = 0
val masked = mask.takeWhile {
if (it == '#') numberOfHashtags++
numberOfHashtags < offsetValue
}
return masked.length + 1
}
override fun transformedToOriginal(offset: Int): Int {
return mask.take(offset.absoluteValue).count { it == '#' }
}
}
}
How to use it:
#Composable
fun DateTextField() {
var date by remember { mutableStateOf("") }
TextField(
value = date,
onValueChange = {
if (it.length <= DATE_LENGTH) {
date = it
}
},
visualTransformation = MaskVisualTransformation(DATE_MASK)
)
}
object DateDefaults {
const val DATE_MASK = "##/##/####"
const val DATE_LENGTH = 8 // Equals to "##/##/####".count { it == '#' }
}
I would suggest not only a date mask, but a simpler and generic solution for inputs masking.
A general formatter interface in order to implement any kind of mask.
interface MaskFormatter {
fun format(textToFormat: String): String
}
Implement our own formatters.
object DateFormatter : MaskFormatter {
override fun format(textToFormat: String): String {
TODO("Format '01212022' into '01/21/2022'")
}
}
object CreditCardFormatter : MaskFormatter {
override fun format(textToFormat: String): String {
TODO("Format '1234567890123456' into '1234 5678 9012 3456'")
}
}
And finally use this generic extension function for transforming your text field inputs and you won't need to care about the offsets at all.
internal fun MaskFormatter.toVisualTransformation(): VisualTransformation =
VisualTransformation {
val output = format(it.text)
TransformedText(
AnnotatedString(output),
object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int = output.length
override fun transformedToOriginal(offset: Int): Int = it.text.length
}
)
}
Some example usages:
// Date Example
private const val MAX_DATE_LENGTH = 8
#Composable
fun DateTextField() {
var date by remember { mutableStateOf("") }
TextField(
value = date,
onValueChange = {
if (it.matches("^\\d{0,$MAX_DATE_LENGTH}\$".toRegex())) {
date = it
}
},
visualTransformation = DateFormatter.toVisualTransformation()
)
}
// Credit Card Example
private const val MAX_CREDIT_CARD_LENGTH = 16
#Composable
fun CreditCardTextField() {
var creditCard by remember { mutableStateOf("") }
TextField(
value = creditCard,
onValueChange = {
if (it.matches("^\\d{0,$MAX_CREDIT_CARD_LENGTH}\$".toRegex())) {
creditCard = it
}
},
visualTransformation = CreditCardFormatter.toVisualTransformation()
)
}
It is because you are checking for the length of the string. Whenever the length is two, you insert a slash. Hence the slash gets deleted, and re-inserted.
Why don't you just create three TextFields and insert Slashes as Texts in between. Such logic can be very hard to perfect. Keen users can use it to crash your app, and also devs can insert malicious stuff, and exploit this flaw because the handling logic can have loopholes as well, so... It is better in my opinion to just go with the simplest (and what I think is more elegant) way of constructing.

Android Multi-row summation: Request for code shortening

I have a table with fifteen rows. Each row have three columns and a total column. I want to get the total per row, the grand total, and the overall average.
The user may not enter data for all rows, and the user may skip a row.
So the code checks if the user have entered data in one of three fields of each row.
If the row is blank, ignore it.
If some of the fields are filled-up, tell the user to fill up the rest of the row.
If all the fields in a row is filled up, sum all its fields and increment the divider.
I have only pasted the codes for Rows 1 & 2 for brevity, but it shows the gist of what I'm trying to achieve:
The code:
var a1 = 0
var a2 = 0
var total = 0
var divider = 0
// Row 1
if (b1p1.text.isNotEmpty() or b2p1.text.isNotEmpty() or b3p1.text.isNotEmpty()) {
var y = 0
listOf(b1p1, b2p1, b3p1).forEach {
if (it.text.isEmpty()) {
it.error = "Fill up empty fields!"
y = 1
}
}
if (y == 0) {
divider++
listOf(b1p1, b2p1, b3p1).forEach {
a1 += it.text.toString().toInt()
}
total1.text = a1.toString()
total += a1
e2 = 1
} else {
Toast.makeText(activity, "Error", Toast.LENGTH_SHORT).show()
}
}
// Row 2
if (b1p2.text.isNotEmpty() or b2p2.text.isNotEmpty() or b3p2.text.isNotEmpty()) {
var y = 0
listOf(b1p2, b2p2, b3p2).forEach {
if (it.text.isEmpty()) {
it.error = "Fill up empty fields!"
y = 1
}
}
if (y == 0) {
divider++
listOf(b1p2, b2p2, b3p2).forEach {
a2 += it.text.toString().toInt()
}
total2.text = a2.toString()
total += a2
} else {
Toast.makeText(activity, "Error", Toast.LENGTH_SHORT).show()
}
}
if (e2 == 1) {
grandTotalTextView.text = total.toString()
average = total.toDouble()/divider
val decimalFormatter = DecimalFormat("#,###.##")
averageTextView.text = decimalFormatter.format(average).toString()
cyeSingleton.anct3b = decimalFormatter.format(average).toString()
} else {
Toast.makeText(activity, "Error 2", Toast.LENGTH_SHORT).show()
}
The table:
This is the best I could come up with. Should there be no other suggestion, I will settle for this.
Thanks in advance!
**EDIT: Thanks to ** https://stackoverflow.com/users/3736955/jemshit-iskenderov
data class TotalResult(val divider:Int, val allTotal:Int, val showError:Boolean)
private fun calculateTotalResult(allTextViews:List<List<TextView>>, totalTextViews:List<TextView>): TotalResult {
var divider = 0
var allTotal = 0
var showError=false
allTextViews.forEachIndexed{index, rowTextViews->
val rowResult = calculateRowResult(rowTextViews as List<EditText>, totalTextViews[index])
if(!rowResult.ignoreRow){
if(rowResult.allFieldsFilled){
divider+=1
allTotal+=rowResult.rowTotal
}else{
showError = true
}
}
}
Toast.makeText(
activity,
"$divider, $allTotal, $showError", Toast.LENGTH_SHORT)
.show()
return TotalResult(divider, allTotal, showError)
}
data class RowResult(val ignoreRow:Boolean, val allFieldsFilled:Boolean, val rowTotal:Int)
private fun calculateRowResult(rowTextViews:List<EditText>, totalTextView:TextView): RowResult {
val ignore = rowTextViews.filter{it.text.isBlank()}.count() == rowTextViews.size
if(ignore)
return RowResult(true, false, 0)
var emptyFieldCount = 0
var total = 0
rowTextViews.forEach {textView ->
if (textView.text.isEmpty()) {
textView.error = "Fill up empty fields!"
emptyFieldCount +=1
}else{
val fieldValue:Int? = textView.text.toString().toIntOrNull() // or toIntOrElse{0}
if(fieldValue!=null) total+=fieldValue
}
}
if(emptyFieldCount==0)
totalTextView.text = total.toString()
return RowResult(false, emptyFieldCount==0, total)
}
fun main(){
val totalResult = calculateTotalResult(
allTextViews = listOf(
listOf(t11,t12,t13),
listOf(t21,t22,t23)
),
totalTextViews = listOf(totalView1, totalView2)
)
// single Toast error
if(totalResult.showError){
// showToast(error)
}
// use totalResult.divider, totalResult.allTotal
}
data class TotalResult(val divider:Int, val allTotal:Int, val showError:Boolean)
fun calculateTotalResult(allTextViews:List<List<TextView>>, totalTextViews:List<TextView>){
var divider = 0
var allTotal = 0
var showError=false
allTextViews.forEachIndexed{index, rowTextViews->
val rowResult = calculateRowResult(rowTextViews, totalTextViews[index])
if(!rowResult.ignore){
if(rowResult.allFieldsFilled){
divider+=1
allTotal+=rowResult.rowTotal
}else{
showError = true
}
}
}
return TotalResult(divider, allTotal, showError)
}
data class RowResult(val ignoreRow:Boolean, val allFieldsFilled:Boolean, val rowTotal:Int)
fun calculateRowResult(rowTextViews:List<TextView>, totalTextView:TextView): RowResult {
val ignore = rowTextViews.filter{it.isBlank()}.count() == rowTextViews.size
if(ignore)
return RowResult(true, false, 0)
var emptyFieldCount = 0
var total = 0
rowTextViews.forEach {textView ->
if (textView.text.isEmpty()) {
textView.error = "Fill up empty fields!"
emptyFieldCount +=1
}else{
val fieldValue:Int? = textView.text.toString().toIntOrNull() // or toIntOrElse{0}
if(fieldValue!=null) total+=fieldValue
}
}
if(emptyFieldCount==0)
totalTextView.text = total.toString()
return RowResult(false, emptyFieldCount==0, total)
}
Extracted calculateTotalResult() and calculateRowResult() so multiple rows and columns do not need to repeat same code.
calculateRowResult() processes singlet row of TextViews. I had to iterate rowTextViews twice, one to calculate ignore, the other to show error on TextView if not ignore. We don't show Toast Error here yet.
calculateTotalResult() iterates through all rows and gets total result. We show only one Toast Error (if required) after this step.
Code is pseudo-code, not tested.

Getting Issue in xAxis.setValueFormatter with Dynamic value :

I am getting some issue Bar Graph with Dynamic values in X-Axis values setValueFormatter:
I checked with a list view, recycler view and view inflation but always getting issues in the values in X-Axis Formatter that it doesn't work with value when list size is 3 or less than 3.
With View Inflation:
Requirement: I want to set the values in a graph with a set of 8. Like, If my list size is 14,
So the graph view inflates 2 times like:
If my list size is 10, it will inflate 2 times, first with 8 values of bar graph and the second graph only with 2 values. It works for the same when the list size is 9, 8 values for the first graph and 1 value for the second graph.
But the issue is that when the remaining value for the last graph is less than 4 like:
when the list size is 11, the remaining value is 3
when the list size is 25, the remaining value is 1 for the fourth graph
It gives the repeated or duplicated value in the x Axis like this:
Here is the issue:
xAxis.setValueFormatter(object : IAxisValueFormatter {
override fun getFormattedValue(value: Float, axis: AxisBase?): String {
var size : String = ""
try {
if(value>=0&&!mMonths[value.toInt()/10 % mMonths.size].equals("")&&value<mMonths.size*10) {
size = mMonths[0 / 10 % mMonths.size]
}
}catch (e : IndexOutOfBoundsException){
xAxis.isGranularityEnabled=false
}
return size
}
})
Note: This will work fine when mMonths list size is greater than 3 as you can see in the first screenshot but it gives issues for list size 1, 2 or 3.
Source code:
private fun setBarChart(mList:ArrayList) {
var calender_size: Float = 0.0f
if(mList.
size>0){
if(mList.size>8){
if(mList.size/8==0){
calender_size = (mList.size / 8).toFloat()
}else{
calender_size = (mList.size / 8).toFloat() + 1
}
}else{
calender_size = 1f
}
}
var section_variable: Int = 0
var tillvalue: Int = 0
var list_size: Int = 1
allGraphs.clear()
multiplegraphviewtmp.removeAllViews()
for (i in 0 until Math.round(calender_size)) {
val newLayout = layoutInflater.inflate(R.layout.new_functionalgraph, null, false)
multiplegraphviewtmp.addView(newLayout)
allGraphs.add(newLayout)
newLayout.tv_categoryname.setText(mList[0].description)
val entriesnew = ArrayList<BarEntry>()
entriesnew.clear()
var mMonths : ArrayList<String> = ArrayList()
mMonths.clear()
if(mList.size>8){
if (i > 0) {
if (mList.size / 8 == 0) {
list_size = (i + 1) * 8
} else {
val temp = mList.size % 8
if ((i + 1) * 8 < mList.size) {
list_size = (i + 1) * 8
} else {
// section_variable = section_variable + temp
list_size = list_size + temp
}
}
} else {
list_size = 8
}
for (j in section_variable until list_size) {
entriesnew.add(BarEntry((tillvalue * 10).toFloat(), mList[j].percentage.toFloat()))
mMonths.add(mList[j].itemName)
section_variable++
tillvalue++
}
tillvalue = 0
}else{
list_size=1
for (j in 0 until mList.size) {
entriesnew.add(BarEntry((tillvalue * 10).toFloat(), mList[j].percentage.toFloat()))
mMonths.add(mList[j].itemName)
//section_variable++
tillvalue++
}
tillvalue = 0
}
newLayout.barChartmore.legend.isEnabled = false
newLayout.barChartmore.description.isEnabled = false
var leftAxisfornewgraphs = newLayout.barChartmore.getAxisLeft()
leftAxisfornewgraphs.setAxisMaxValue(100f)
leftAxisfornewgraphs.setAxisMinValue(0f)
leftAxisfornewgraphs.setStartAtZero(true)
leftAxisfornewgraphs.setAxisLineWidth(0f)
leftAxisfornewgraphs.setLabelCount(11, true)
leftAxisfornewgraphs.setDrawGridLines(true)
leftAxisfornewgraphs.axisLineColor = activity!!.resources.getColor(R.color.graph_textColor)
leftAxisfornewgraphs.textColor = activity!!.resources.getColor(R.color.graph_textColor)
leftAxisfornewgraphs.typeface = Typeface.DEFAULT_BOLD
leftAxisfornewgraphs.textSize = 12f
leftAxisfornewgraphs.gridColor = activity!!.resources.getColor(R.color.view)
leftAxisfornewgraphs.axisLineColor = activity!!.resources.getColor(R.color.view)
// leftAxisfornewgraphs.setValueFormatter(MyYAxisValueFormatter()) //////////to set labels in %
//////////////////to change Right line/////////////////
var rightAxis = newLayout.barChartmore.axisRight
rightAxis.setDrawGridLines(false)
rightAxis.setDrawLabels(false)
rightAxis.setLabelCount(40, true)
rightAxis.axisLineColor = activity!!.resources.getColor(R.color.view)
var xAxis = newLayout.barChartmore.getXAxis()
xAxis.textSize = 9f
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM)
xAxis.setLabelRotationAngle(-90f)
xAxis.textColor = activity!!.resources.getColor(R.color.graph_textColor)
xAxis.typeface = Typeface.DEFAULT_BOLD
xAxis.gridColor = activity!!.resources.getColor(R.color.view)
xAxis.axisLineColor = activity!!.resources.getColor(R.color.view)
xAxis.granularity=1f
newLayout.barChartmore.setPinchZoom(false)
newLayout.barChartmore.getLegend().setWordWrapEnabled(true)
newLayout.barChartmore.setScaleEnabled(false)
newLayout.barChartmore.isDoubleTapToZoomEnabled=false
val labels = ArrayList<String>()
labels.clear()
for (i in 0 until mMonths.size){
labels.add("18-Jan")
}
xAxis.setValueFormatter(object : IAxisValueFormatter {
override fun getFormattedValue(value: Float, axis: AxisBase?): String {
var size : String = ""
try {
if(value>=0&&!mMonths[value.toInt()/10 % mMonths.size].equals("")&&value<mMonths.size*10) {
size = mMonths[0 / 10 % mMonths.size]
mTotalvalues++
}
}catch (e : IndexOutOfBoundsException){
xAxis.isGranularityEnabled=false
}
return size
}
})
xAxis.spaceMin = 6f
xAxis.spaceMax = 6f
val barDataSet = BarDataSet(entriesnew, "Cells")
barDataSet.setDrawValues(false)
val list: ArrayList<Int> = ArrayList<Int>()
list.clear()
list.add(activity!!.resources.getColor(R.color.graph_skyblue))
list.add(activity!!.resources.getColor(R.color.graph_orange))
list.add(activity!!.resources.getColor(R.color.graph_navyBlue))
barDataSet.setColors(list)
val data = BarData(barDataSet);
if(mMonths.size<2){
data.barWidth= 1f
}else{
data.barWidth= 5f
}
newLayout.barChartmore.data = data
newLayout.barChartmore.invalidate()
newLayout.export_single.setOnClickListener(View.OnClickListener {
Commonclass.createPdf(activity, newLayout.barChartmore.chartBitmap)
})
//newLayout.barChartmore.clear()
}
}
Same Issue getting with recycler view and Listview. please help:
I am getting some issue Bar Graph with Dynamic values in XAxis values setValueFormatter

Generate the same maze with a seed

Im followed a tutorial to create a maze with Recursive Backtracking and it works great.
Im trying to create a game where people get on the same maze, and if someone wins, it creates a new maze and everyones current maze gets updated.
So what i was thinking is to have a seed to create the same maze and pass that seed to all the players so they can have the same maze.
Is there a way to modify it so i can give the maze a seed and it creates always the same maze?
This is what i have now:
It uses a Cell class (posx,posy)
class Cell(var col:Int = 0, var row: Int = 0){
var topWall = true
var leftWall = true
var bottomWall = true
var rightWall = true
var visited = false
}
fun createMaze(){
var stack = Stack<Cell>()
var current:Cell
var next:Cell?
for(x in 0 until COLS){
for(y in 0 until ROWS){
cells[x][y] = Cell(x,y)
}
}
player = cells[0][0]
exit = cells [COLS-1][ROWS-1]
current = cells[0][0]
current.visited = true
do{
next = getNeighbour(current)
if(next != null) {
removeWall(current, next)
stack.push(current)
current = next
current.visited = true
}else{
current = stack.pop()
}
}while (!stack.empty())
}
fun getNeighbour(cell:Cell): Cell? {
var vecinos: ArrayList<Cell> = ArrayList()
//vecino izquierda
if(cell.col > 0) {
if (!cells[cell.col - 1][cell.row].visited) {
vecinos.add(cells[cell.col - 1][cell.row])
}
}
//vecino derecha
if(cell.col < COLS - 1) {
if (!cells[cell.col + 1][cell.row].visited) {
vecinos.add(cells[cell.col + 1][cell.row])
}
}
//vecino arriba
if(cell.row > 0) {
if (!cells[cell.col][cell.row - 1].visited) {
vecinos.add(cells[cell.col ][cell.row - 1])
}
}
//vecino abajo
if(cell.row < ROWS - 1) {
if (!cells[cell.col][cell.row + 1].visited) {
vecinos.add(cells[cell.col][cell.row + 1])
}
}
if (vecinos.size > 0) {
var index = random.nextInt(vecinos.size)
return vecinos[index]
}else {
return null
}
}
fun removeWall(current:Cell,next:Cell){
if (current.col == next.col && current.row == next.row +1){
current.topWall = false
next.bottomWall = false
}
if (current.col == next.col && current.row == next.row -1){
current.bottomWall = false
next.topWall = false
}
if (current.col == next.col + 1 && current.row == next.row){
current.leftWall = false
next.rightWall = false
}
if (current.col == next.col - 1 && current.row == next.row){
current.rightWall = false
next.leftWall = false
}
}
If you want to pass a seed to create the maze, then you have to make sure that all of the players are using the same random number generator. Which means you have to supply your own random number generator implementation.
The application would seed the random number generator with the value you pass, and then it should deterministically generate the same sequence of random numbers for each client.
Note also that you can't ever change the random number generator implementation unless you can prove that the new implementation will generate exactly the same sequence of numbers that the original did.

Categories

Resources