实施“懒惰排序”


44

我应该对数字列表进行排序,但是我很懒。很难弄清楚如何交换所有数字,直到所有数字都按升序排列,所以我想出了自己的算法来保证新列表的排序。运作方式如下:

对于大小为N的列表,我们需要N-1次迭代。在每次迭代中

  • 检查第N个数字是否小于第N + 1个数字。如果是,则这两个数字已经排序,因此我们可以跳过此迭代。

  • 如果不是,那么您需要连续递减前N个数字,直到这两个数字顺序正确为止。

让我们举一个具体的例子。假设输入是

10 5 7 6 1

在第一个迭代中,我们将比较10和5。10大于5,因此我们将其递减直到更小:

4 5 7 6 1

现在我们比较5和7。5小于7,因此在此迭代中无需执行任何操作。因此,我们转到下一个并比较7和6。7大于6,因此我们递减前三个数字,直到小于6,我们得到:

2 3 5 6 1

现在我们比较6和1。同样,6大于1,因此我们将前四个数字减1,直到小于1,我们得到:

-4 -3 -1 0 1

我们完成了!现在,我们的列表已按完美排序。而且,为了使事情变得更好,我们只需要遍历列表N-1次,因此该算法以O(N-1)时间对列表进行排序,我敢肯定,这是最快的算法。²

今天的挑战是实现这种惰性排序。您的程序或函数将获得任意喜欢的标准格式的整数数组,并且您必须执行此惰性排序并返回新的“已排序”列表。数组永远不会为空或包含非整数。

这里有些例子:

Input: 10 5 7 6 1
Output: -4 -3 -1 0 1

Input: 3 2 1
Output: -1 0 1

Input: 1 2 3
Output: 1 2 3

Input: 19
Output: 19

Input: 1 1 1 1 1 1 1 1 1 
Output: -7 -6 -5 -4 -3 -2 -1 0 1 

Input: 5 7 11 6 16 2 9 16 6 16
Output: -27 -25 -21 -20 -10 -9 -2 5 6 16

Input: -8 17 9 7
Output: -20 5 6 7

和往常一样,这是,因此,请写出最短的程序!


¹ 这并不意味着听起来像什么,但这在技术上是真实的

² 我在开玩笑,请不要恨我


6
我想,如果你这样做,你不懒
约尔格Hülsermann

4
@JörgHülsermann好吧,一些整数太重了……不完全有心情承受如此重的重量,最好只摘掉最上面的东西
Erik the Outgolfer

21
<sarcasm>实际上,这种排序算法仍然会O(N^2)耗时,因为您必须遍历列表中所有以前访问的项目才能减少它们。我建议倒排整个列表,并在必要时每步只减少一个数字。这将为您带来真正的O(N)复杂性!</sarcasm>
Value Ink

1
O(n^2)就内存访问而言,@ ValueInk 是不是O(n)用于比较?
科尔·约翰逊

7
@ColeJohnson从技术上是可以的,但是时间复杂度需要考虑算法的所有步骤。您仍然必须在每次迭代中遍历所有先前的索引,因此它仍然会出来O(N^2)
Value Ink

Answers:


12

果冻 14 12 11  9 字节

-2字节归功于ETHproductions(使用最小二倍数«

I’«0Ṛ+\Ṛ+

用于获取和返回整数列表的单子链接。

在线尝试!或查看测试套件

我真的认为这还不够Lazy™!

怎么样?

I’«0Ṛ+\Ṛ+ - Link: list of integers, a              e.g. [ 8, 3, 3, 4, 6, 2]
I         - increments between consecutive items of a   [-5, 0, 1, 2,-4 ]
 ’        - decrement (vectorises)                      [-6,-1, 0, 1,-5 ]
   0      - literal 0
  «       - minimum of decremented increments and zero  [-6,-1, 0, 0,-5 ]
    Ṛ     - reverse                                     [-5, 0, 0,-1,-6 ]
      \   - cumulative reduce with:
     +    -   addition                                  [-5,-5,-5,-6,-12]
       Ṛ  - reverse                                     [-12,-6,-5,-5,-5]
        + - addition (with a)                           [-4,-3,-2,-1, 1, 2]


8

JavaScript(ES6),61个字节

a=>a.map((b,i)=>a=(b-=a[i+1])>0?a.map(c=>i--<0?c:c-b-1):a)&&a

测试用例


7

果冻,12字节

I»1U
0ị;Ç_\U

在线尝试!

这个怎么运作

I»1U  Helper link. Argument: l (list of integers)
I     Compute the increments (difference between items) of l.
 »1   For each item n, take the maximum of n and 1.
   U  Reverse.

0ị;Ç_\U  Main link. Argument: l (list of integers)
   Ç     Call the helper link with argument l.
  ;      Concatenate this with
0ị       the 0th last item of the (1-indexed) l. (Can't use Ṫ because it modifies l)
    _\   Cumulatively reduce the result by subtraction.
      U  Reverse.

基本的思路是这样的:如果反转输入和输出数组,则输出只是输入,每个增量为0或更大的值被-1取代。例如:

[10,  5,  7,  6,  1]   input
[ 1,  6,  7,  5, 10]   reverse
[   5,  1, -2,  5  ]   deltas
[  -1, -1, -2, -1  ]   min(deltas, -1)
[ 1, -1, -2, -1, -1]   reverse and concat the last item of the original
[ 1,  0, -2, -3, -4]   re-apply deltas
[-4, -3, -2,  0,  1]   reverse

5

k,20个字节

{x-|+\0,1_0|1+-':|x}

在线尝试。

说明:

{                  } /function, x is input
                 |x  /reverse x
              -':    /difference between every element
            1+       /add one to each difference
          0|         /make minimum difference be 0
      0,1_           /swap first difference with a 0
    +\               /cumulative sum
   |                 /reverse again
 x-                  /subtract from x

4

Haskell,56个字节

a#(x:y:z)=map(+min(y-x-1)0)(a++[x])#(y:z)
a#x=a++x
([]#)

在线尝试!

将列表的第一部分保留在parameter中a。在每一步中,将下一个元素添加x到的末尾,a并将a的所有元素增加最小(y-x-1)0


4

Python,54个字节

f=lambda a,*r:r and[f(*r)[0]-max(r[0]-a,1)]+f(*r)or[a]

在线尝试!

接受像一样的输入f(1,2,3)。输出列表。使用指数时间。


3

C#,76个字节

a=>{for(int d=0,i=a.Length-1;i>0;a[--i]-=d)d=a[i-1]-d<a[i]?d:a[i-1]-a[i]+1;}

这将修改列表。它向后浏览该列表,并保持运行的增量总数适用于每个数字。


2

JavaScript(ES6),59个字节

f=([n,...a],p=a[0]-n)=>a+a?[(a=f(a))[0]-(p>1?p:1),...a]:[n]

哇。我本来打算写一个JS解决方案,但是后来我看到了。我不认为在参数中使用像这样的传播算子
andrewarchi

f=可以省去JS答案以节省两个字节
andrewarchi

@andrewarchi谢谢,但是这个特定的函数需要调用自身(f(a)),因此它仍然需要名称。
ETHproductions

我忘记了它是递归的
andrewarchi

2

Brain-Flak,153个字节

{(({})<>[({})])(({}({}))[({}[{}])])<>(([{}]({})))([({}<(())>)](<>)){({}())<>}{}{((<{}>))<>{}}{}<>{}{{}({}<>{}())((<>))}{}{}}{}<>{}([]){{}({}<>)<>([])}<>

在线尝试!

这包括+1用于-r标志。

#While True
{

    #Push the last value left in the array minus the counter onto the alternate stack
    (({})<>[({})])

    #Put the counter back on top of the alternate stack
    (({}({}))[({}[{}])])

    #Toggle
    <>

    #Find the difference between the last two inputs left on the array
    (([{}]({})))

    #Greater than or equal to 0?
    ([({}<(())>)](<>)){({}())<>}{}{((<{}>))<>{}}{}<>{}

    #If So:
    {

      #Pop the truthy/falsy value
      {}

      #Increment the counter by the difference between elements +1
      ({}<>{}())

      #Push two falsys
      ((<>))

    #Endwhile
    }

    #Pop the two falsys
    {}{}

#Endwhile
}

#Pop the falsy

{}

#Toggle back
<>

#Pop the counter

#Reverse the stack
{}
([]){{}({}<>)<>([])}<>

2

R,56个字节

function(s){s-c(rev(cumsum(rev(pmax(0,-diff(s)+1)))),0)}


1
很好地使用diff,我试图弄清楚该如何工作...顺便说一句,您可以在-2个字节处摆脱函数主体周围的花括号,但更好的是,您可以使用s=scan()代替函数定义以节省更多字节。如果您包括“ 在线试用”的链接,这样其他人可以验证该代码适用于所有测试用例,那就太好了。
朱塞佩

别担心!我们都从某个地方开始:)
朱塞佩

1

JavaScript(ES6),68个字节

a=>a.map((v,i)=>(d=v-o[i+1]+1)>1?o=o.map((v,j)=>j>i?v:v-d):0,o=a)&&o

输入和输出是整数数组。

测试片段

f=
a=>a.map((v,i)=>(d=v-o[i+1]+1)>1?o=o.map((v,j)=>j>i?v:v-d):0,o=a)&&o
<input id=I oninput="O.value=f(this.value.split` `.map(x=>+x)).join` `">
<input id=O disabled>


1

JavaScript(ES6),50个字节

f=a=>(b=[...a]).some((_,i)=>a[i]-->=a[i+1])?f(a):b

说明:

这是一种递归解决方案,它首先克隆数组,然后减小所有值,直到一个元素大于或等于数组中的下一个元素。

只要任何元素不正常,该函数就会调用自身。当元素最终排序后,将返回克隆。(我们不能返回数组本身,因为该some()方法将减少其所有元素,使它们全部减-1。)

测试用例:

f=a=>(b=[...a]).some((_,i)=>a[i]-->=a[i+1])?f(a):b

console.log(f([10,5,7,6,1])+'');
console.log(f([1,1,1,1,1,1,1,1,1])+'');
console.log(f([5,7,11,6,16,2,9,16,6,16])+'');
console.log(f([19])+'');
console.log(f([-8,17,9,7])+'');
console.log(f([1,2,3,4,5,6,7])+'');


1

SWI-Prolog,194个字节

:-use_module(library(clpfd)).
f([],[],_,_).
f([A|B],[M|N],P,D):-A#=M-D-E,A#<P,abs(M,S),T#=S+1,E in 0..T,label([E]),f(B,N,A,D+E).
l([],[]).
l(A,B):-reverse(Z,B),f([X|Y],Z,X+1,0),reverse(A,[X|Y]).

可以在这里在线尝试:http : //swish.swi-prolog.org/p/LazySort.pl

您问l(L, [10,5,7,6,1]).哪个说“解决L,其中L是此列表的惰性排序版本”。

这两个功能是:

  • lazysorted(A,B)-声明A是B的惰性排序版本,如果它们都是空列表,或者如果A可以通过反转B来获得,则调用helper函数以遍历列表并使用累加器进行减法将每个值都推到比前一个值低的位置,然后将结果反转回正确的位置。
  • fhelper匹配两个列表,即列表中前一个数字的值和一个滚动差异累加器,并求解当前列表位置的新值是原始值减去差异累加器,还可以选择减去强制执行此操作所需的新值值低于列表中前一个数字,并且f必须使用现在增加的差异累加器递归求解列表的尾部。

Swish上的测试用例的屏幕截图:

该图显示了在Swish上运行的测试用例


0

JavaScript(ES6),61个字节

a=>a.reduceRight((r,e)=>[e-(d=(c=e-r[0]+1)>d?c:d),...r],d=[])

不是最短的解决方案,但我无法放弃使用的机会reduceRight


0

C#(.NET Core)89 88 86 79字节

  • 使用稍微不同的方法仅保存了1个字节。
  • 使用fors 的简化形式另外保存了2个字节。
  • 由于VisualMelon出色的高尔夫技巧,节省了7个字节。
a=>{for(int i=0,j,k;++i<a.Length;)for(k=a[i-1]-a[j=i]+1;--j>=0;)a[j]-=k>0?k:0;}

在线尝试!

首先for迭代数组,然后计算递减,最后在for必要时第二次递减元素,直到第ith个位置。

仅修改原始数组而不返回新数组(仍然习惯规则)是否有效?


是的,完全可以修改原始数组。:)
DJMcMayhem

4
@DJMcMayhem谢谢,我懒得创建一个新的。:)
查理(Charlie)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.