Kotlin conditional formatting string - string

I have three variables :
val months: Long
val days: Long
val hours: Long
and I want to return something like this :
3 months, 2 days and 5 hours
Now this would simply translate to as :
val str = "$months months, $days days and $hours hours"
And if my months had to be 0 and days as 1 and hours as 0 then it will come like '0 months, 1 days and 0 hours'
But what I am looking for is "1 days" instead. How can I get it done?
I can definitely use some sort of conditional StringBuilder to get this done, but is there something better and elegant?

How does this look to you?
fun formatTime(months: Long, days: Long, hours: Long): String =
listOf(
months to "months",
days to "days",
hours to "hours"
)
.filter { (length,_) -> length > 0L }
// You can add an optional map predicate to make singular and plurals
.map { (amount, label) -> amount to if(abs(amount)==1L) label.replace("s", "") else label }
.joinToString(separator=", ") { (length, label) -> "$length $label" }
.addressLastItem()
fun String.addressLastItem() =
if(this.count { it == ','} >= 1)
// Dirty hack to get it working quickly
this.reversed().replaceFirst(" ,", " dna ").reversed()
else
this
You can see it working over here

Another variant without replacing, counting or reversing the list:
fun formatTime(months: Long, days: Long, hours: Long): String {
val list = listOfNotNull(
months.formatOrNull("month", "months"),
days.formatOrNull("day", "days"),
hours.formatOrNull("hour", "hours"),
)
return if (list.isEmpty()) "all values <= 0"
else
listOfNotNull(
list.take(list.lastIndex).joinToString().takeIf(String::isNotEmpty),
list.lastOrNull()
).joinToString(" and ")
}
fun Long.formatOrNull(singular: String, plural: String = "${singular}s") = when {
this == 1L -> "$this $singular"
this > 1L -> "$this $plural"
else -> null
}
It also has a fallback if all values are <= 0... you could also just use an empty string or whatever you prefer.
If you do not like that there are intermediate lists created to concatenate the string, you may also just use something as follows instead in the else path:
list.iterator().run {
buildString {
while (hasNext()) {
val part = next()
if (length > 0)
if (hasNext())
append(", ")
else
append(" and ")
append(part)
}
}
}

Related

Find matches in different indices in String. Kotlin

I have two lines of the same length. I need to get the number of letters that match as letters and have different index in the string (without nesting loop into loop). How I can do it?
Would the following function work for you?
fun check(s1: String, s2: String): Int {
var count = 0
s2.forEachIndexed { index, c ->
if (s1.contains(c) && s1[index] != c) count++
}
return count
}
See it working here
Edit:
Alternatively, you could do it like this if you want a one-liner
val count = s1.zip(s2){a,b -> s1.contains(b) && a != b}.count{it}
where s1 and s2 are the 2 strings

Is there a more efficient way to reverse a integer number (both positive and negative) in SCALA?

I am making a program in SCALA that takes a integer number and reverses it. For example, an input of 30 returns an output of 3. This program must also work for negative numbers, For instance, an input of -89 returns an output of -98. Also, if in the reversal the first digit is 0, it should be truncated (30 to 3). This is the code I have written.
import io.StdIn._
val twoDigitNumber : Int = takeInput()
println("The reversal is " + reverse(twoDigitNumber))
//define a function name reverse to handle the actual reverse process for -ve and +ve numbers
def reverse(x: Integer): Integer = {
//4 possibilities: +ve, 1st digit 0; -ve, 1st digit zero; -ve, 1st digit not zero; +ve, 1st digit not zero
if (x> 0 && x.toString.reverse.charAt(0) == 0) {
x.toString.reverse.substring(1).toInt
} else if (x<0 && x.toString.substring(1).reverse.charAt(0) == 0) {
('-' + x.toString.substring(1).reverse.substring(1)).toInt
} else if (x<0 && x.toString.substring(1).reverse.charAt(0)!= 0) {
('-'+ x.toString.substring(1).reverse).toInt
} else {
x.toString.reverse.toInt
}
}
//reads an integer number
def takeInput() : Int ={
print("Enter a two-digit integer number: ")
readInt()
}
Is there a more efficient way to do this?
The shortest I found:
x.signum * x.abs.toString.reverse.toInt
It can be like below considering x is your integer input:
val reverseOutput = if (x>0) x.toString.reverse.toInt else -1* ((x * -1).toString.reverse.toInt)
def reverseANumber(n: Int): Int = {
def _reverseANumber(i: Int, i1: Int): Int = i match
case 0 => i1
case i =>
val n = i % 10
val n1 = n * math.pow(10, (((math.log10(i) + 1).toInt) - 1)).toInt
_reverseANumber(i / 10, i1 + n1)
_reverseANumber(n, 0)
}

Print characters at even and odd indices from a String

Using scala, how to print string in even and odd indices of a given string? I am aware of the imperative approach using var. I am looking for an approach that uses immutability, avoids side-effects (of course, until need to print result) and concise.
Here is a tail-recursive solution returning even and odd chars (List[Char], List[Char]) in one go
def f(in: String): (List[Char], List[Char]) = {
#tailrec def run(s: String, idx: Int, accEven: List[Char], accOdd: List[Char]): (List[Char], List[Char]) = {
if (idx < 0) (accEven, accOdd)
else if (idx % 2 == 0) run(s, idx - 1, s.charAt(idx) :: accEven, accOdd)
else run(s, idx - 1, accEven, s.charAt(idx) :: accOdd)
}
run(in, in.length - 1, Nil, Nil)
}
which could be printed like so
val (even, odd) = f("abcdefg")
println(even.mkString)
Another way to explore is using zipWithIndex
def printer(evenOdd: Int) {
val str = "1234"
str.zipWithIndex.foreach { i =>
i._2 % 2 match {
case x if x == evenOdd => print(i._1)
case _ =>
}
}
}
In this case you can check the results by using the printer function
scala> printer(1)
24
scala> printer(0)
13
.zipWithIndex takes a List and returns tuples of the elements coupled with their index. Knowing that a String is a list of Char
Looking at str
scala> val str = "1234"
str: String = 1234
str.zipWithIndex
res: scala.collection.immutable.IndexedSeq[(Char, Int)] = Vector((1,0), (2,1), (3,2), (4,3))
Lastly, as you only need to print, using foreach instead of map is more ideal as you aren't expecting values to be returned
You can use the sliding function, which is quite simple:
scala> "abcdefgh".sliding(1,2).mkString("")
res16: String = aceg
scala> "abcdefgh".tail.sliding(1,2).mkString("")
res17: String = bdfh
val s = "abcd"
// ac
(0 until s.length by 2).map(i => s(i))
// bd
(1 until s.length by 2).map(i => s(i))
just pure functions with map operator

Count how many times a string repeats itself SCALA

Hi I´m trying to get the number of times an artist name repeats in some years for this I have this
var artists=Array.ofDim[String](994,2)//artist,year
var artists2=Array.ofDim[String](250)//artist name
var artists3 = Array.ofDim[Int](250)//number of times
And the user has to enter ano1 and ano2 that are the years margin we want
val loop = new Breaks;
for(i <- 0 to 993){//copiamos
loop.breakable{
for(j<- 0 to 249){
if(artists2(j).contentEquals("NULL") && artists(i)(1).toInt>=ano1 && artists(i)(1).toInt<=ano2){
artists2(j)=artists(i)(0)
artists3(j)= 1
loop.break;
}else if(artists(i)(0).contentEquals(artists2(j)) && artists(i)(1).toInt>=ano1 && artists(i)(1).toInt<=ano2){
artists3(j)= artists3(j)+1
loop.break;
}
}
}
}
println(artists2.mkString("\n"))
println(artists3.mkString(","))
For some reason my if doesnt work or j add itself 1 after entering in the if because every time is creating a new element in artists2 instead of adding it to artists3
The output I get is artists3 filled with 1 because for some reason it never checks the other part of the if
I'm sorry if this sounds a bit harsh, but there are so many things wrong with your code it's hard to know where to begin.
The main problems are 1) your code isn't very scala-like, and 2) you're using data structures and variable names designed to make things as difficult as possible to understand.
Here's a brief attempt to redesign things. It may not meet all your requirements but perhaps it will start you in a better direction.
val mockdata = List( ("Tom", 2001)
, ("Sue", 2002)
, ("Joe", 2002)
, ("Sue", 2005)
, ("Sue", 2004)
, ("Jil", 2001)
, ("Tom", 2005)
, ("Sue", 2002)
, ("Jil", 2012)
)
def countArtists( dataSet: List[(String,Int)]
, anoStart: Int , anoEnd: Int): Map[String,Int] = {
val artists = for {
(artist, year) <- dataSet
if year >= anoStart && year <= anoEnd
} yield artist
artists.distinct.map(name => name -> artists.count(_ == name)).toMap
}
val count2002to2011 = countArtists(mockdata, 2002, 2011)
At this point you can use the result to get interesting information.
scala> count2002to2011.keys // all artists within the time period
res0: Iterable[String] = Set(Sue, Joe, Tom)
scala> count2002to2011.values.sum // total count within the time period
res1: Int = 6
scala> count2002to2011("Sue") // count for just this artist
res2: Int = 4
your implementation seems a little bit confused, if i understood what you needed, i would like if i may, share how i would have implemented your research, in a more functionnal/scala and clear way.
case class Artist(name: String, year: Int)
case class ArtistNames(name: String)
case class ArtistResult(name: String, nbOccurence: Int)
val anno1 = 1950
val anno2 = 1960
def checkArtist(artists: Seq[Artist], artistNames: Seq[ArtistNames]): Seq[ArtistResult] = {
artists.map{ artist =>
def countOccurence(artist: Artist, artistNames: Seq[ArtistNames], occurence: Int): Int = {
artistNames match {
case Nil => occurence
case head :: tail =>
if (head.name == artist.name && artist.year >= anno1 && artist.year <= anno2) countOccurence(artist, tail, occurence + 1)
else countOccurence(artist, tail, occurence)
}
}
val occurrence = countOccurence(artist, artistNames, 0)
ArtistResult(artist.name, occurrence)
}
}
val artistResultList: Seq[ArtistResult] = checkArtist(/* insert your data here */)
i hope i answered a little bit your question.

Truncate text to get preview in Scala

I need to truncate a text to get a preview. The preview is the text prefix of ~N chars (but not more) and it should not split words in the middle.
preview("aaa", 10) = "aaa"
preview("a b c", 10) = "a b c"
preview("aaa bbb", 5) = "aaa"
preview("a b ccc", 3) = "a b"
I coded a function as follows:
def preview(s:String, n:Int) =
if (s.length <= n) s else s.take(s.lastIndexOf(' ', n))
Would you change or fix it ?
Now I am thinking how to handle the case when the text words are separated by one or more white spaces (including \n,\t, etc.) rather than just a single space. How would you improve the function to handle this case ?
How about the following:
def preview(s: String, n: Int) = if (s.length <= n) {
s
} else {
s.take(s.lastIndexWhere(_.isSpaceChar, n + 1)).trim
}
This function will:
For the strings shorter or equal n return the string (no preview required)
Otherwise find the the last space character in the n + 1 first characters (this will indicate whether the last world is being split, as if it's not than n + 1 will be a space chracter and otherwise a non-space character) and take a string up to this point
Note: The usage of isSpaceChar will not only provide support for space, but also new line or paragraph, which is what I believe you're after (and you can replace it with isWhitespace if you're after even more extended set of word separators).
I propose next one:
-- UPDATED--
def ellipsize(text : String, max : Int): String = {
def ellipsize0(s : String): String =
if(s.length <= max) s
else {
val end = s.lastIndexOf(" ")
if(end == -1) s.take(max)
else ellipsize0(s.take(end))
}
ellipsize0("\\s+".r.replaceAllIn(text, " "))
}
Or your (modified):
def preview(str : String, n : Int) = {
(s : String) => if (s.length <= n) s else s.take(s.lastIndexOf(' ', n))
}.apply( "\\s+".r.replaceAllIn(str, " "))
How about this
def preview(s:String, n:Int) =
if (s.length <= n) s
else s.take(n).takeWhile(_ != ' ')
Try it here: http://scalafiddle.net/console/a05d886123a54de3ca4b0985b718fb9b
This seems to work:
// find the last word that is not split by n, then take to its end
def preview(text: String, n: Int): String =
text take (("""\S+""".r findAllMatchIn text takeWhile (_.end <= n)).toList match {
case Nil => n
case ms => ms.last.end
})
An alternative take (pun intended) but doesn't like input of all whitespace:
text take (("""\S+""".r findAllMatchIn text takeWhile (m => m.start == 0 || m.end <= n)).toList.last.end min n)
Extensionally:
object Previewer {
implicit class `string preview`(val text: String) extends AnyVal {
// find the last word that is not split by n, then take to its end
def preview(n: Int): String =
text take (("""\S+""".r findAllMatchIn text takeWhile (_.end <= n)).toList match {
case Nil => n
case ms => ms.last.end
})
}
}
Looks nice that way:
class PreviewTest {
import Previewer._
#Test def shorter(): Unit = {
assertEquals("aaa", "aaa" preview 10)
}
#Test def spacey(): Unit = {
assertEquals("a b c", "a b c" preview 10)
}
#Test def split(): Unit = {
assertEquals("abc", "abc cba" preview 5)
}
#Test def onspace(): Unit = {
assertEquals("a b", "a b cde" preview 3)
}
#Test def trimming(): Unit = {
assertEquals("a b", "a b cde" preview 5)
}
#Test def none(): Unit = {
assertEquals(" " * 5, " " * 8 preview 5)
}
#Test def prefix(): Unit = {
assertEquals("a" * 5, "a" * 10 preview 5)
}
}

Resources