Какъв е добър и функционален начин за размяна на елементи на колекция в Scala?

В един мой проект продължава да се появява един общ случай на употреба. В един момент имам сортирана колекция от някакъв вид (списък, последователност и т.н.... няма значение) и един елемент от тази колекция. Това, което искам да направя, е да разменя дадения елемент със следващия му елемент (ако този елемент съществува) или понякога с предходния елемент.

Добре съм запознат с начините за постигане на това чрез техники за процедурно програмиране. Въпросът ми е какъв би бил добър начин за решаване на проблема с помощта на функционално програмиране (в Scala)?


Благодаря на всички за отговорите. Приех това, което самият аз разбирах най-много. Тъй като (все още) не съм функционален програмист, малко ми е трудно да реша кой отговор е наистина най-добрият. Всички те са доста добри според мен.


person Andreas Eisele    schedule 26.07.2010    source източник
comment
Гледахте ли това? rosettacode.org/wiki/Generic_swap#Scala   -  person James Black    schedule 26.07.2010


Отговори (5)


Следното е функционалната версия на swap със следващия елемент в списък, вие просто създавате нов списък с разменени елементи.

def swapWithNext[T](l: List[T], e : T) : List[T] = l match {
  case Nil => Nil
  case `e`::next::tl => next::e::tl
  case hd::tl => hd::swapWithNext(tl, e)
}
person venechka    schedule 26.07.2010
comment
вашият отговор прави точно това, което възнамерявах. благодаря ти, че ми напомни за шаблона. - person Andreas Eisele; 27.07.2010

Zipper е чиста функционална структура от данни с указател към тази структура. Казано по друг начин, това е елемент с контекст в някаква структура.

Например библиотеката Scalaz предоставя Zipper клас, който моделира списък с определен елемент от списък на фокус.

Можете да получите цип за списък, фокусиран върху първия елемент.

import scalaz._
import Scalaz._

val z: Option[Zipper[Int]] = List(1,2,3,4).toZipper

Можете да преместите фокуса на ципа с помощта на методи на Zipper, например можете да преместите към следващото изместване от текущия фокус.

val z2: Option[Zipper[Int]] = z >>= (_.next)

Това е като List.tail с изключение на това, че помни къде е било.

След това, след като поставите избрания от вас елемент на фокус, можете да промените елементите около фокуса.

val swappedWithNext: Option[Zipper[Int]] =
  for (x <- z2;
       y <- x.delete)
    yield y.insertLeft(x.focus)

Забележка: това е с най-новата стволова глава на Scalaz, в която е коригиран бъг с рекурсивните find и move методи на Zipper.

След това методът, който искате, е просто:

def swapWithNext[T](l: List[T], p: T => Boolean) : List[T] = (for {
  z <- l.toZipper
  y <- z.findZ(p)
  x <- y.delete
} yield x.insertLeft(y.focus).toStream.toList) getOrElse l

Това съответства на елемент, базиран на предикат p. Но можете да отидете по-далеч и да вземете предвид и всички близки елементи. Например, за прилагане на сортиране чрез вмъкване.

person Apocalisp    schedule 27.07.2010
comment
Много добро обяснение на Zipper. Благодаря ти. Винаги съм се чудил какво става с тези. ;) - person Andreas Eisele; 27.07.2010
comment
Би било хубаво, ако можете да предоставите пълната функция, която взема колекция и елемент и разменя първото появяване на този елемент в колекцията със следващия. - person Daniel C. Sobral; 09.08.2010

Обща версия на Landei:

import scala.collection.generic.CanBuildFrom
import scala.collection.SeqLike
def swapWithNext[A,CC](cc: CC, e: A)(implicit w1: CC => SeqLike[A,CC],
                                        w2: CanBuildFrom[CC,A,CC]): CC = {
    val seq: SeqLike[A,CC] = cc
    val (h,t) = seq.span(_ != e)
    val (m,l) = (t.head,t.tail)
    if(l.isEmpty) cc
    else (h :+ l.head :+ m) ++ l.tail
}

някои употреби:

scala> swapWithNext(List(1,2,3,4),3)
res0: List[Int] = List(1, 2, 4, 3)

scala> swapWithNext("abcdef",'d')
res2: java.lang.String = abcedf

scala> swapWithNext(Array(1,2,3,4,5),2)
res3: Array[Int] = Array(1, 3, 2, 4, 5)

scala> swapWithNext(Seq(1,2,3,4),3)
res4: Seq[Int] = List(1, 2, 4, 3)

scala>
person Eastsun    schedule 26.07.2010

Алтернативно изпълнение за метода на venechka:

def swapWithNext[T](l: List[T], e: T): List[T] = {
  val (h,t) = l.span(_ != e)
  h ::: t.tail.head :: e :: t.tail.tail
}

Имайте предвид, че това се проваля с грешка, ако e е последният елемент.

Ако знаете и двата елемента и всеки елемент се среща само веднъж, става по-елегантно:

def swap[T](l: List[T], a:T, b:T) : List[T] = l.map(_ match {
  case `a` => b
  case `b` => a
  case e => e }
)
person Landei    schedule 26.07.2010

Какво ще кажеш :

  val identifierPosition = 3;
  val l = "this is a identifierhere here";
  val sl = l.split(" ").toList;

  val elementAtPos = sl(identifierPosition)
  val swapped = elementAtPos :: dropIndex(sl , identifierPosition)

  println(swapped)

  def dropIndex[T](xs: List[T], n: Int) : List[T] = {
    val (l1, l2) = xs splitAt n
    l1 ::: (l2 drop 1)
  }

поздравления за http://www.scala-lang.org/old/node/5286 за функция dropIndex

person blue-sky    schedule 10.08.2015