Home > front end >  Given: aabcdddeabb => Expected: [(a,2),(b,1),(c,1),(d,3),(e,1),(a,1),(b,1)] in Scala
Given: aabcdddeabb => Expected: [(a,2),(b,1),(c,1),(d,3),(e,1),(a,1),(b,1)] in Scala

Time:02-21

I'm really interested in how this algorithm can be implemented. If possible, it would be great to see an implementation with and without recursion. I am new to the language so I would be very grateful for help. All I could come up with was this code and it goes no further:

print(counterOccur("aabcdddeabb"))

def counterOccur(string: String) =
string.toCharArray.toList.map(char => {
  if (!char.charValue().equals(char.charValue()   1)) (char, counter)
  else (char, counter   1)
})

I realize that it's not even close to the truth, I just don't even have a clue what else could be used.

CodePudding user response:

First solution with using recursion. I take Char by Char from string and check if last element in the Vector is the same as current. If elements the same I update last element by increasing count(It is first case). If last element does not the same I just add new element to the Vector(second case). When I took all Chars from the string I just return result.

  def counterOccur(string: String): Vector[(Char, Int)] = {
    @tailrec
    def loop(str: List[Char], result: Vector[(Char, Int)]): Vector[(Char, Int)] = {
      str match {
        case x :: xs if result.lastOption.exists(_._1.equals(x)) =>
          val count = result(result.size - 1)._2
          loop(xs, result.updated(result.size - 1, (x, count   1)))
        case x :: xs =>
          loop(xs, result :  (x, 1))
        case Nil => result
      }
    }

    loop(string.toList, Vector.empty[(Char, Int)])
  }

  println(counterOccur("aabcdddeabb"))

Second solution that does not use recursion. It works the same, but instead of the recursion it is using foldLeft.

  def counterOccur2(string: String): Vector[(Char, Int)] = {
    string.foldLeft(Vector.empty[(Char, Int)])((r, v) => {
      val lastElementIndex = r.size - 1
      if (r.lastOption.exists(lv => lv._1.equals(v))) {
        r.updated(lastElementIndex, (v, r(lastElementIndex)._2   1))
      } else {
        r :  (v, 1)
      }
    })
  }

  println(counterOccur2("aabcdddeabb"))

CodePudding user response:

Here is a solution using foldLeft and a custom State case class:

def countConsecutives[A](data: List[A]): List[(A, Int)] = {
  final case class State(currentElem: A, currentCount: Int, acc: List[(A, Int)]) {
    def result: List[(A, Int)] =
      ((currentElem -> currentCount) :: acc).reverse
    
    def nextState(newElem: A): State =
      if (newElem == currentElem)
        this.copy(currentCount = this.currentCount   1)
      else
        State(
          currentElem = newElem,
          currentCount = 1,
          acc = (this.currentElem -> this.currentCount) :: this.acc
        )
  }
  object State {
    def initial(a: A): State =
      State(
        currentElem = a,
        currentCount = 1,
        acc = List.empty
      )
  }
  
  data match {
    case a :: tail =>
      tail.foldLeft(State.initial(a)) {
        case (state, newElem) =>
          state.nextState(newElem)
      }.result
    
    case Nil =>
      List.empty
  }
}

You can see the code running here.

CodePudding user response:

You can use a very simple foldLeft to accumulate. You also don't need toCharArray and toList because strings are implicitly convertible to Seq[Char]:

"aabcdddeabb".foldLeft(collection.mutable.ListBuffer[(Char,Int)]()){ (acc, elm) =>
   acc.lastOption match {
     case Some((c, i)) if c == elm => 
       acc.dropRightInPlace(1).addOne((elm, i 1))
     case _ => 
       acc.addOne((elm, 1))
   }
}

CodePudding user response:

For

str = "aabcdddeabb"

you could use extract matches of the regular expression

rgx = /(.)\1*/

to obtain the array

["aa", "b", "c", "ddd", "e", "a", "bb"]

Then simply map each element s of the array to the desired string.


In Ruby, for example, you could write

arr = str.gsub(rgx).to_a
  #=> ["aa", "b", "c", "ddd", "e", "a", "bb"]
arr.map { |s| "(#{s[0]},#{s.size})" }  
  #=> ["(a,2)", "(b,1)", "(c,1)", "(d,3)", "(e,1)", "(a,1)", "(b,2)"]

I assume Scala, like most languages, has comparable methods or functions.

Note that I've employed a little-used form of Ruby's string method gsub. Rather than making character substitutions it simply returns an an enumerator that generates matches of the regular expression. Those matches are converted to an array of matches with the the method to_a. Ruby also has a method scan but that does not have the intended effect here because the regular expression contains a capture group.

  • Related