How would be a functional approach to shifting certain array elements?

后端 未结 9 2054
感动是毒
感动是毒 2021-01-05 07:03

I have a Scala app with a list of items with checkboxes so the user select some, and click a button to shift them one position up (left). I decided to write a function to sh

相关标签:
9条回答
  • 2021-01-05 07:36

    I don't claim this stuff below to be efficient or readable. Unfortunately, all the good answers seem to be taken, so I'm going for originality. :-)

    def shift[T](a: Seq[T], p: T => Boolean) = {
      val (areP, notP) = a.zipWithIndex partition { case (t, index) => p(t) }
      val shifted = areP map { case (t, index) => (t, index - 1) }
      val others = notP map (shifted.foldLeft(_){
        case ((t, indexNotP), (_, indexIsP)) => 
          if (indexNotP == indexIsP) (t, indexNotP + 1) else (t, indexNotP)
      })
      (shifted ++ others).sortBy(_._2).map(_._1)
    }
    

    So, here's what's happening. First, I associate each character with its index (a.zipWithIndex), and then separate then into areP and notP depending on whether the character satisfies p or not.

    So, at this point, I have two sequences, each composed of a character and its index in the original sequence.

    Next, I simply shift the index of the elements in the first sequence, by subtracting 1, and compute shifted.

    Computing the new index of the unshifted elements is much harder. For each of those elements (notP map), I'll do a foldLeft. The accumulator of the fold left will be the element itself (always with its index). The sequence that is being folded is the sequence of shifted elements -- so one can see that for each unshifted element, I traverse the whole sequence of shifted elements (highly inefficient!).

    So, we compare the index of the unshifted element to the index of each shifted element. If they are equal, increase the index of the unshifted element. Because the sequence of shifted elements is ordered (partition doesn't change the order), we know that we'll test first for lower indices, and then for higher indices, guaranteeing that an element will have its index increased as much as necessary.

    With that, we join the two sequences, order them by their new indices, and then map back to the element.

    0 讨论(0)
  • 2021-01-05 07:37

    Here's a purely functional implementation

    def shiftElements[A](l: List[A], pred: A => Boolean): List[A] = {
      def aux(lx: List[A], accum: List[A]): List[A] = {
        lx match {
          case Nil => accum
          case a::b::xs if pred(b) && !pred(a) => aux(a::xs, b::accum)
          case x::xs => aux(xs, x::accum)
        }
      }
      aux(l, Nil).reverse
    }
    

    And here's one that uses mutability on the inside to be faster

    import scala.collection.mutable.ListBuffer
    def shiftElements2[A](l: List[A], pred: A => Boolean): List[A] = {
      val buf = new ListBuffer[A]
      def aux(lx: List[A]) {
        lx match {
          case Nil => ()
          case a::b::xs if pred(b) && !pred(a) => {
            buf.append(b)
            aux(a::xs)
          }
          case x::xs => {
            buf.append(x)
            aux(xs)
          }
        }
      }
      aux(l)
      buf.toList
    }
    
    0 讨论(0)
  • 2021-01-05 07:38

    You could probably do this via a foldLeft (also known as /:):

    (str(0).toString /: str.substring(1)) { (buf, ch) =>
        if (ch.isUpper) buf.dropRight(1) + ch + buf.last  else buf + ch
    }
    

    It needs work to handle the empty String but:

    def foo(Str: String)(p: Char => Boolean) : String = (str(0).toString /: str.substring(1)) { 
       (buf, ch) => if (p(ch) ) buf.dropRight(1) + ch + buf.last else buf + ch
    }
    
    val pred = (ch: Char) => ch.isUpper
    foo("abcDEfghI")(pred) //prints abDEcfgIh
    

    I'll leave it as an exercise as to how to modify this into the array-based solution

    0 讨论(0)
提交回复
热议问题