Каким будет функциональный подход к перемещению определенных элементов массива?

У меня есть приложение Scala со списком элементов с флажками, чтобы пользователь выбрал некоторые, и нажмите кнопку, чтобы переместить их на одну позицию вверх (слева). Я решил написать функцию для перемещения элементов некоторого произвольного типа, которые удовлетворяют заданному предикату. Итак, если у вас есть эти элементы:

a b c D E f g h I

а предикат - "символы верхнего регистра", функция вернет это:

a b D E c f g I h

Короче говоря, любая последовательность смежных элементов, которые соответствуют предикату, обменивается одним элементом слева от него.

Я придумал следующую уродливую императивную реализацию. Я хотел бы видеть приятное и, надеюсь, читаемое функциональное решение.

def shiftUp[T](a:Array[T], shiftable: T => Boolean) = {
    val s = new Array[T](a.length)
    var i = 0
    var j = 0
    while(i < a.length)
    {
        if(!shiftable(a(i)) && i < a.length - 1 && shiftable(a(i+1)))
        {
            var ii = i + 1
            while(ii < a.length && shiftable(a(ii)))
            {
                s(j) = a(ii)
                ii = ii+1
                j = j+1
            }
            s(j) = a(i)
            i = ii
        }
        else
        {
            s(j) = a(i)
            i = i+1
        }
        j = j+1
    }
    s
}

EDIT: Спасибо всем, надеюсь, вам понравилось упражнение!

Ответы

Ответ 1

Здесь чисто функциональная реализация

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
}

И вот тот, который использует изменчивость внутри, чтобы быть быстрее

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
}

Ответ 2

Возможно, вы можете сделать это с помощью foldLeft (также известного как /:):

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

Ему нужна работа для обработки пустой строки, но:

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

Я оставлю это как упражнение относительно того, как изменить это в решении на основе массива

Ответ 3

Это в основном императивный алгоритм с функциональным стилем.

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
}

Он изменяет массив на месте с побочным эффектом. Я думаю, что это действительно похоже на версию в вопросе, за исключением того, что ее легче читать. Императив не должен быть уродливым...

Изменить: darn, не мог заснуть, пока я не написал это (как и выше, просто более компактно):

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
}

Ответ 4

Не самый быстрый, но не ограничиваясь этим, String и использующий ту же логику, что и @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)

Ответ 5

Я не утверждаю, что этот материал ниже, чтобы быть эффективным или читаемым. К сожалению, все хорошие ответы, кажется, принимаются, поэтому я собираюсь за оригинальность.: -)

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)
}

Итак, вот что происходит. Во-первых, я связываю каждый символ с его индексом (a.zipWithIndex), а затем разделяю затем на areP и notP в зависимости от того, удовлетворяет ли символ p или нет.

Итак, в этот момент у меня есть две последовательности, каждая из которых состоит из символа и его индекса в исходной последовательности.

Затем я просто сдвигаю индекс элементов в первой последовательности, вычитая 1 и вычисляю shifted.

Вычисление нового индекса несмещенных элементов намного сложнее. Для каждого из этих элементов (notP map) я сделаю a foldLeft. Аккумуляром оставленной левой части будет сам элемент (всегда с его индексом). Последовательность, которая складывается, представляет собой последовательность сдвинутых элементов - поэтому можно видеть, что для каждого неперемещенного элемента я пересекаю всю последовательность сдвинутых элементов (очень неэффективно!).

Итак, мы сравниваем индекс неперемещенного элемента с индексом каждого сдвинутого элемента. Если они равны, увеличьте индекс неперемещенного элемента. Поскольку упорядоченная последовательность сдвинутых элементов упорядочена (partition не изменяет порядок), мы знаем, что сначала будем тестировать более низкие индексы, а затем для более высоких индексов, гарантируя, что элемент будет иметь свой индекс, увеличится столько же, сколько необходимо.

С этим мы присоединяем две последовательности, упорядочим их по их новым индексам, а затем вернемся к элементу.

Ответ 6

Здесь другой вариант ответа Джеффа:

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
  }
}

Быстрое тестирование с использованием

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()

Здесь версия 2

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
  }
}

Ответ 7

Я не знаю достаточно, чтобы написать его в Scala, но эта проблема специально разработана для функций списка takeWhile и dropWhile. Идея состоит в том, что вы разделили список предметов на три части:

  • Левая часть, вычисленная с помощью takeWhile, содержит самые левые элементы, не удовлетворяющие предикату.

  • Средняя часть - это группа элементов, которые вы хотите сдвинуть влево, вычисляемые путем удаления левых элементов, а затем takeWhile остаток.

  • Правая часть - это все, что осталось; dropWhile средние элементы.

Здесь он находится в 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

И здесь один unit test:

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

Программисты Haskell могут использовать List.break или List.span для объединения вызовов в takeWhile и dropWhile, но я не уверен, что Scala имеет эти вещи. Кроме того, takeWhile и dropWhile являются красивыми значимыми именами, тогда как я по крайней мере нахожу break и span менее проницательным.

EDIT: фиксированный рекурсивный вызов сделать shift p right вместо right для перемещения всех групп.

Ответ 8

Изменить: это фактически не решает поставленную проблему - она ​​решает связанную, но другую проблему (нажимая приоритет отмеченных элементов на один). Однако я оставляю его здесь для справки.


Здесь "однострочный", используя запрошенные массивы, для 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)

Я оставляю это как упражнение для читателя, чтобы выяснить, как это работает. (Если вы действительно хотите генерики с T, в Scala 2.8 он даст вам GenericArray, тогда вы можете toArray его, если вы хотите потенциально-примитивный массив Java.)

Ответ 9

Решение в J:

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

Позволяет разбить это на именованные части для более легкого понимания. Окончательная строка "abDEcfgIh" является результатом применения функции к строке "abcDEfghI", которая является правильным аргументом функции. Пара алфавитов составляет левый аргумент функции (которая является частью начала) (4 :... "). Таким образом, вместо двухэлементного вектора вложенных в штуку строк мы можем назвать каждый отдельно:

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

Теперь, когда мы имеем две переменные "lc" и "uc" для нижних и верхних алфавитов, давайте подробно рассмотрим тело функции. Принимая логически когерентный кусок с правого конца, так как сначала это оценивалось, мы могли бы назвать это так:

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

Это определяет "rmUCshift" как нечто, что требует правого и левого аргументов ( "4 :" указывает это), когда тело начинается со следующей строки и продолжается до открытого закрывающего пара. Форма "4 : 0", за которой следует тело, представляет собой вариант формы "4 :" тело ", показанный на начальном этапе. Этот глагол rmUCshift можно вызвать независимо следующим образом:

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

Вызов имеет отступ в три пробела, и результат сразу же следует за ним. Левый аргумент (lc;'') представляет собой двухэлементный вектор с пустым массивом, указанным как второй элемент, потому что он не используется в этом фрагменте кода - мы могли бы использовать любое значение после точки с запятой, но две одинарные кавычки легко вводить.

Ниже перечислены следующие фрагменты (определения, за которыми следуют примеры):

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

Объединение этих названных фрагментов дает нам следующее:

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

Однако повторение "x rmUCshift y" не является необходимым и может быть упрощено, чтобы дать нам следующее:

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