我有一个带有复选框的项目列表的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
}
编辑:谢谢大家,我希望你喜欢这个练习!
答案 0 :(得分:12)
这是一个纯粹的功能实现
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
}
答案 1 :(得分:6)
您可以通过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
我将把它留作如何将其修改为基于阵列的解决方案的练习
答案 2 :(得分:2)
这基本上是具有功能风格的命令式算法。
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
}
它修改了Array的位置,带有副作用。我认为它确实像问题中的版本一样,除了它更容易阅读。势在必行并不一定难看......
编辑: 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
}
答案 3 :(得分:1)
不是最快的,但不限于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)
答案 4 :(得分:1)
我不认为下面这些内容有效或可读。不幸的是,所有好的答案似乎都被采纳了,所以我要追求创意。 : - )
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
),我将执行foldLeft
。左侧的累加器将是元素本身(始终带有索引)。正在折叠的序列是移位元素的序列 - 因此我可以看到,对于每个未移位的元素,我遍历整个移位元素序列(非常低效!)。
因此,我们将未移位元素的索引与每个移位元素的索引进行比较。如果它们相等,则增加未移位元素的索引。因为移位元素的序列是有序的(partition
不会改变顺序),我们知道我们将首先测试较低的索引,然后测试更高的索引,保证元素的索引会增加为必要时。
有了它,我们加入两个序列,按它们的新索引排序,然后映射回元素。
答案 5 :(得分:1)
以下是杰夫回答的另一个变种:
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()
这是第二版
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
}
}
答案 6 :(得分:1)
我不太清楚在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
这是一个单元测试:
*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
不那么明显。
编辑:修复递归调用以执行shift p right
而不是right
来提升所有群组。
答案 7 :(得分:0)
编辑:这实际上并没有解决提出的问题 - 它解决了一个相关但不同的问题(将标记项目的优先级提高一个)。不过,我将它留在这里作为参考。
对于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;如果你想要一个Java潜在的原始数组,你就可以使用它。)
答案 8 :(得分:0)
J:
中的解决方案 ('abcdefghijklmnopqrstuvwxyz';'ABCDEFGHIJKLMNOPQRSTUVWXYZ') (4 : '(y#~y e. >1{x)([: I. '' ''= ])} }._1&|.&.((1,~y e. >0{x)&#)y,'' ''') 'abcDEfghI'
abDEcfgIh
让我们把它分成命名的部分,以便更容易理解。最后一个字符串“abDEcfgIh
”是将函数应用于字符串“abcDEfghI
”的结果,该字符串是函数的正确参数。这对字母组成了函数的左参数(它是开始的部分“(4 :
...”)。因此,我们可以单独命名每个字符串,而不是盒装字符串的2元素矢量:
'lc uc'=. 'abcdefghijklmnopqrstuvwxyz';'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
现在我们有两个变量“lc
”和“uc
”用于小写和大写字母,让我们详细检查函数体。从右端获取逻辑上连贯的块,因为这将首先进行评估,我们可以这样命名:
rmUCshift=: 4 : 0
}._1&|.&.((1,~y e. >0{x)&#)y,' '
)
这将“rmUCshift
”定义为需要左右参数(“4 :
”指定此内容)的内容,正文从下一行开始并继续到裸露的结束paren。 “4 : 0
”形式,后跟身体,是最初显示的“4 :
'body'”形式的变体。这个动词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