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

后端 未结 9 2053
感动是毒
感动是毒 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:11

    This is basically an imperative algorithm with a functional style.

    def shifWithSwap[T](a: Array[T], p: T => Boolean) = {
      def swap(i:Int, j:Int) = {
        val tmp = a(i); a(i) = a(j); a(j) = tmp
      }
      def checkAndSwap(i:Int) = i match {
        case n if n < a.length - 1 && !p(a(i)) && p(a(i+1)) => swap(i, i+1)
        case _ =>
      }
      (0 until a.length - 1) map checkAndSwap
      a
    }
    

    It modifies the Array in place, with a side effect. I think it really does it like the version in the question, except it's easier to read. Imperative does not have to be ugly...

    Edit: darn, couldn't fall asleep until I wrote this down (same as above, just more compact):

    def shift[T](a: Array[T], p: T => Boolean) = {
      for (i <- 0 until a.length - 1; if !p(a(i)) && p(a(i+1))) {
        val tmp = a(i); a(i) = a(i+1); a(i+1) = tmp // swap
      }
      a
    }
    
    0 讨论(0)
  • 2021-01-05 07:13

    Edit: this doesn't actually solve the posed problem--it solves a related but different problem (bumping up the priority of marked items by one). I'm leaving it here for reference, however.


    Here's a "one-liner", using arrays as requested, for Scala 2.8.

    def shiftUp[T](a: Array[T], p: T => Boolean) = {
      a.zipWithIndex.map(ci => {
        (ci._1 , if (p(ci._1)) ci._2 - 1.5 else ci._2.toDouble)
      }).sortWith((l,r) => l._2 < r._2).map(_._1)
    }
    
    scala> shiftUp(Array('h','E','l','l','O'),(c:Char)=>c.isUpper).toArray
    res0: Array[Char] = Array(E, h, l, O, l)
    
    scala> shiftUp("HeLlO".toArray,(c:Char)=>c.isUpper).toArray
    res1: Array[Char] = Array(H, L, e, O, l)
    

    I leave it as an exercise to the reader to figure out how it works. (If you really want generics with T, in Scala 2.8 it's going to give you an GenericArray; you then can toArray it if you want a Java potentially-primitive array.)

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

    I don't know enough to write it in Scala, but this problem is tailor-made for the list functions takeWhile and dropWhile. The idea is that you split the list of items into three parts:

    • Left part, computed with takeWhile, contains leftmost elements not satisfying the predicate.

    • Middle part is the group of elements you want to shift left, computed by dropping the left elements and then takeWhile the remainder.

    • Right part is everything left over; dropWhile the middle elements.

    Here it is in Haskell:

    -- take first group of elements satisfying p and shift left one
    shift :: (a -> Bool) -> [a] -> [a]
    shift p l = case reverse left of 
                  []     -> l
                  (a:as) -> reverse as ++ middle ++ a : shift p right
      where left    = takeWhile (not . p) l  -- could be done with List.break
            notLeft = dropWhile (not . p) l
            middle  = takeWhile p notLeft    -- could be done with List.span
            right   = dropWhile p notLeft
    

    And here's a single unit test:

    *Shiftup> shift (>9) [1, 2, 3, 44, 55, 6, 7, 8]
    [1,2,44,55,3,6,7,8]
    

    Haskell programmers might use List.break or List.span to combine calls to takeWhile and dropWhile, but I'm not sure if Scala has these things. Besides, takeWhile and dropWhile are nice meaningful names, whereas I at least find break and span less perspicuous.

    EDIT: fixed recursive call to do shift p right instead of right to shift up all groups.

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

    Here's another variant on Geoff's answer:

    def shift[T](l: List[T], p: T => Boolean): List[T] = {
      l match {
        case a::b::t if ! p(a) && p(b) => b::shift(a::t, p)
        case a::t => a::shift(t, p)
        case Nil => l
      }
    }
    

    Quickly tested using

    scala> def pred(c: Char) = c.isUpper
    pred: (c: Char)Boolean
    
    scala> shift("abcDEfghI".toList, pred)
    res3: List[Char] = List(a, b, D, E, c, f, g, I, h)
    
    scala> shift("AbCd".toList, pred)
    res4: List[Char] = List(A, C, b, d)
    
    scala> shift(Nil, pred)
    res5: List[Nothing] = List()
    

    Here's version two

    def shift[T](l: List[T], p: T => Boolean, r: List[T] = Nil): List[T] = {
      l match {
        case a::b::t if ! p(a) && p(b) => shift(a::t, p, b::r)
        case a::t => shift(t, p, a::r)
        case Nil => r.reverse
      }
    }
    
    0 讨论(0)
  • 2021-01-05 07:32

    A solution in J:

       ('abcdefghijklmnopqrstuvwxyz';'ABCDEFGHIJKLMNOPQRSTUVWXYZ') (4 : '(y#~y e. >1{x)([: I. '' ''= ])} }._1&|.&.((1,~y e. >0{x)&#)y,'' ''') 'abcDEfghI'
    abDEcfgIh
    

    Let’s break this into named pieces for easier comprehension. The final string “abDEcfgIh” is the result of applying a function to the string “abcDEfghI” which is the right argument to the function. The pair of alphabets constitute the left argument to the function (which is the part beginning “(4 :…”). So, instead of the 2-element vector of boxed strings, we could name each one individually:

       'lc uc'=. 'abcdefghijklmnopqrstuvwxyz';'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    

    Now that we have two variables “lc” and “uc” for the lower-case and upper-case alphabets, let’s examine the body of the function in detail. Taking a logically coherent chunk from the right end, since this would be evaluated first, we could name this like so:

       rmUCshift=: 4 : 0
       }._1&|.&.((1,~y e. >0{x)&#)y,' '
    )
    

    This defines “rmUCshift” as something that requires a right and left argument (the “4 :” specifies this) with the body beginning on the next line and continuing to the bare closing paren. The “4 : 0” form, followed by the body, is a variant of the “4 :‘body’” form shown initially. This verb rmUCshift can be invoked independently like this:

       (lc;'') rmUCshift 'abcDEfghI'  NB. Remove upper-case, shift, then insert
    ab  cfg h                         NB. spaces where the upper-case would now be.
    

    The invocation is indented three spaces and the output immediately follows the it. The left argument (lc;'') is a two-element vector with the empty array specified as the second element because it’s not used in this piece of the code – we could have used any value after the semicolon but the two single quotes are easy to type.

    The next pieces to name are these (definitions followed by examples):

      ixSpaces=: [:I.' '=]
      ixSpaces 'ab  cfg h'
    2 3 7
       onlyUC=: 4 : 'y#~y e.>1{x'
       ('';uc) onlyUC 'abcDEfghI'
    DEI
    

    Combining these named pieces together gives us this:

       (lc;uc) (4 : '(x onlyUC y)(ixSpaces x rmUCshift y)}x rmUCshift y') 'abcDEfghI'
    abDEcfgIh
    

    However, the repetition of “x rmUCshift y” is unnecessary and can be simplified to give us this:

       (lc;uc) (4 : '(x onlyUC y) ((ixSpaces ]) } ]) x rmUCshift y') 'abcDEfghI'
    abDEcfgIh
    
    0 讨论(0)
  • 2021-01-05 07:34

    Not the fastest, but not limited to String and using the same logic as @oxbow_lakes

    def shift[T](iter: Iterable[T])(p: T=>Boolean): Iterable[T] = 
      iter.foldLeft(Iterable[T]())((buf, elm) => 
        if (p(elm) && buf.nonEmpty) 
          buf.dropRight(1) ++ Iterable[T](elm) ++ Iterable[T](buf.last) 
        else 
          buf++Iterable[T](elm)
      )
    
    def upperCase(c:Char)=c.isUpper
    
    shift("abcDEfghI")(upperCase).mkString
        //scala> res86: String = abDEcfgIh
    
    val array="abcDEfghI".toArray
    shift(array)(upperCase).toArray
        //res89: Array[Char] = Array(a, b, D, E, c, f, g, I, h)
    
    def pair(i:Int)=i%2==0
    val l=List(1,2,3,5,4,6,7,9,8)
    shift(l)(pair)
        //scala> res88: Iterable[Int] = List(2, 1, 3, 4, 6, 5, 7, 8, 9)
    
    0 讨论(0)
提交回复
热议问题