没有合适的资源?快使用搜索试试~ 我知道了~
函数的折叠和展开条件及其应用
146《理论计算机科学电子札记》44卷第1期(2001)网址:http://www.elsevier.nl/locate/entcs/volume44.html15页什么时候函数是折叠还是展开?杰里米·吉本斯牛津大学计算实验室,Wolfson Building,Parks Road,Oxford OX1 3QD,英国格雷厄姆·赫顿语言和编程组,计算机科学和IT学院,诺丁汉大学,JubileeCampus,Wollaton Road,Nottingham NG8 1BB,英国托尔斯滕·阿尔滕基希语言和编程组,计算机科学和IT学院,诺丁汉大学,JubileeCampus,Wollaton Road,Nottingham NG8 1BB,英国摘要本文给出了一个集合论函数可以用递归算子fold表示的充要条件,以及递归算子founder的对偶条件。这些条件简单、实用,并且在底层数据类型中是通用的。1介绍递归操作符fold封装了一个通用模式,用于定义使用最小定点类型(如finite list)值的双重地,递归操作符unfold封装了一个用于定义程序的常见模式,这些程序产生最大定点类型的值,例如流(无限列表)。褶皱的理论和应用比比皆是-最近的调查见[11,4]-而近年来越来越清楚的是,不太知名的展开概念也同样有用[5,6,10,13,15]。考虑到对折叠和展开的兴趣,很自然地会问,什么时候可以使用这些操作符之一来编写程序。令人惊讶的是,人们对这个问题知之甚少。本文对程序是集合之间的全函数这一特殊情况给出了完整的回答特别地,我们给出了一个集合论函数可以用fold表示的充要条件和一个展开的对偶条件。条件很简单,实际上2000年1月,出版社dbyElsevierScienceB。 V.操作访问和C CB Y-NC-ND许可证。吉本斯,赫顿和阿滕基希147→→有用的,并且在底层数据类型中是通用的。然而,我们的证明是集理论的,并利用经典逻辑和公理的选择,因此我们的结果不推广到范畴的建设性功能1。2折叠和展开在这一节中,我们回顾了折叠和展开在初始代数和最终余代数方面的范畴处理;更多细节见[18,20,14,1]。假设我们固定一个范畴C和一个函子F:C → C。 一个代数是由对象A和箭头f组成的对(A,f):F A→A,从一个这样的代数到另一个这样的代数的同态h:(A,f)→(B,g)是箭头h:A→B,使得下面的平方交换:F AFhFBFGJ JAhB初始代数是以代数为对象、同态为箭头的范畴中的初始对象。 我们写(μF,in)为一个初始代数,并折叠f为从初始代数到任何其他代数(A,f)的唯一同态h:(μF,in)(A,f)。也就是说,foldf被定义为唯一的箭头,使得以下平方交换:F(f倍)F(µF)FA在fJJµFfoldfA余代数、余同态和终端余代数的对偶概念也有类似的定义。对于一个终结余代数,我们记为(νF,out),对于唯一的余同态h,我们展开f:(A,f)(νF,out)从任何余代数(A,f)到终端余代数。也就是说,unfoldf被定义为唯一的箭头,使以下平方交换:A展开f νFFoutJF(JF AF(展开f)νF)在文献中,folddf和unfolddf有时写作(|F|)和[(f)|你好,[1]如有效拓扑或ω-集范畴吉本斯,赫顿和阿滕基希148N×→×S → S×分别称为变质作用和变质作用。2.1示例:有限列表假设我们定义一个函子L:ETLA的ET=1+(N并且Lf=id1+(idN其中N是自然数的集合。则代数是由集合A和函数f组成的对(A,f):1+(NA)、A.这类函数对于其他函数g:1→A和h:N ×A→A总是可以唯一地分解成f = [ g,h ]的形式。一个同态f:(A,[g,h])→(B,[i,j])是函数f:A→B使得f·g=i且f·h= j·(idN×f)。函子L有一个初始代数(µL,in)=(List(),[nil,cons]),其中List(A)是所有元素取自A的有限列表的集合,nil:1→List(N)和cons:N×List(N)→List(N)是这个集合的构造函数给定任何其他集合A和两个函数i:1→A和j:N×A→A,函数fold[i,j]:List(N)→A由以下两个等式唯一定义fold[i,j]·nil=ifold[i,j]·cons=j·(idN×fold[i,j])也就是说,fold[i,j]通过将列表末尾的nil构造函数替换为函数i,并将列表中的每个cons构造函数替换为函数j来处理列表。例如,对自然数列表求和的函数sum:List(N)→N可以定义为sum=fold[zero,plus],其中zero:1→N和plus:N× N→ N由zero()= 0和plus(x,y)=x+y给出。我们将在稍后的示例中使用此数据类型。为了符号简单,我们将nil()写成因此,我们可以将上述折叠的定义更清楚地写为:(fold[i,j])[]=i(fold[i,j])(x:xs)=j(x,(fold[i,j])xs)2.2示例:流假设我们定义一个函子S:SET→ SET为SA=N×A,Sf=idN×f。则余代数是包含集合A和a函数f:A→N×A。这类函数总是可以唯一地分解为f=g,h的形式,对于某些其他函数g:A→N和h:A→A。上同态f:(A ,ng ,hn )→(B ,ni ,jn )是一个函数f:A→B 使得i·f=g 和j·f=f·h。函子S有一个终结余代数(νS,out)= (Stream (N),head,tail),其中Stream(A)是所有从A抽取元素的流的集合,而head:Stream(N)→N和tail:Stream(N)→Stream(N)是这个集合的析构函数。 给定任何其他集合A和两个函数g:A→ N和h:A→A,函数unfold_g,h_f:A→Stream(N)由以下唯一定义:吉本斯,赫顿和阿滕基希149⟨⟩⇐⇒··→·下面两个等式:头·展开角g,h=gtail ·unfoldg,h=unfoldg,h·h也就是说,展开g,h通过使用函数g产生流的头部,并且函数h产生另一个值,然后以相同的方式展开该值以产生流的尾部来产生流。 例如,函数from:N→Stream(N),它产生一个以1为步长递增的自然数流,可以定义为from=unfold_idN,succ_n,其中succ:N→ N由succ x=x+1给出。3什么时候箭头是折叠还是展开?fold运算符封装了一个用于定义类型为µF→A的箭头的通用模式。这是自然的,然后问,当一个箭头的这种类型可以写使用折叠。更准确地说,对于另一个箭头f:FA→A,什么时候可以把任意的箭头h:μF→ A写成h=foldf的形式?一个技术上完整的,但仍然不令人满意的,这个问题的答案是由折叠算子的普适性质提供的[18],它可以被描述为以下等价:h=foldf惠h·in=f·F h这个等价的方向说明foldf是从初始代数(μF,in)到另一个代数(A,f)的同态,而方向说明这两个代数之间的任何其他同态h必须等于foldf。总的来说,普适性表达了这样一个事实:折叠f是从(μF,in)到(A,f)的唯一同态。泛性质为我们的问题提供了一个完整的答案--当hin=f F h时,h可以精确地写成foldf的形式--但它并没有那么有用,因为它要求我们已经知道f。然而,给定一个特定的h,泛性质通常可以用来指导构造一个适当的f[11],但我们也不认为这是一个完全令人满意的答案,因为这种方法只是一种启发式的,有时很难在实践中应用。泛属性的问题在于它涉及h的内涵方面,即构成其实现的一部分的函数f。通常,基于纯粹外延方面的条件更有用。对我们的问题的一个部分回答是,每个左可逆箭头h:µF→A都可以用fold来写[20]。形式上,如果我们假设存在一个箭头g:A µF,使得gh=idµF,那么方程h=foldf可以如下求解:吉本斯,赫顿和阿滕基希150→→→→→→→h=foldf优惠 {universal property}h·in =f·F h优惠 {identities}h·in·idF(µF)=f·F h优惠{functors}h·in·F(idµF)=f·F h优惠 {假设}h·in·F(g·h)=f·F h优惠{functors}h·in·F g·F h=f·F h替代 性(Substitution)f=h·in·F g综上所述,我们得出了以下结论:g·h=idµFh=倍数(h·in·F g)例如,函数rev:List(N)反转列表的List(N)是它自己的逆,因此通过上面的含义可以直接使用fold来写rev。然而,请注意,这一含义只提供了我们问题的部分答案,因为反过来一般不成立。也就是说,不是每个可以用fold写成的箭头h:µF A都是左可逆的。例如,函数sum:List(N)N在上一节中使用了fold,但不是左可逆的。对偶地,unfold算子也满足一个普适性质,它可以用来证明每个A νF类型的右可逆箭头都可以用unfold来写[20]。例如,函数evenpos:Stream(N)Stream(N)从流中删除每一个其他元素,它有一个右逆(任何在流中每一个相邻对之间插入一个元素的函数),因此可以直接使用unfold编写evenpos。然而,不是每个可以用unfold写成的箭头h:A νF都是右可逆的。例如,函数from:NStream(N)在上一节中使用unfold编写,但不是右可逆的。据我们所知,上面的可逆性结果是唯一已知的结果,说明了正确类型的任意箭头何时可以使用折叠或展开来书写。在本节的最后,我们指出,在特定种类的箭头方面已经取得了更多的进展。例如,融合定律指出,同态和折叠的合成可以吉本斯,赫顿和阿滕基希151S→总是被写成一个fold,而香蕉分割定律指出,应用于同一个论点的两个fold总是可以写成一个fold[20]。4什么时候一个函数是一个折叠?在这一节中,我们给出了一个必要和充分的条件,当一个箭头可以用折叠写,对于特殊情况的范畴ET,其中的箭头是集之间的全函数。我们对结果进行二元化,以便在下一节中展开结果取决于以下定义:定义4.1函数f的核[17]:A B是由f标识的元素对的集合:kerf={(a,AJ)∈A×A|fa= faJ}这一节的主要结果是一个充要条件,说明了当SET中的任意箭头h:μF→A对于另一个箭头f:F A→A可以写成h=foldf的形式时。定理4.2设h:μF → A。然后(图:F A → A。h= fold g)惠克(F h)惠克(h·in)(另一种说法是,h是一个折叠ikerh是在in下的一个同余;也就是说,将关系提升记为关系R在A上的一个关系的Rel(F)(R),i(x,y)∈Rel(F)(kerh)意味着(inx,iny)∈kerh。证明的关键是众所周知的观察,即包含内核等价于“后因子”的存在引理4.3假设f:A → B和h:A → C。然后(图:B→C。 h=g·f)惠(kerfkerhB→C/=)证据 证据很简单。对于m方向,假设g:B→C和h=g·f;那么显然B→C/=g,更进一步,吉本斯,赫顿和阿滕基希152·/·∅联系我们联系我们→/联系我们(a,aJ)∈kerf优惠 {kernels}fa=faJ替代 性(Substitution)g(fa)=g(faJ)优惠 {h=g·f}ha=h aJ优惠 {kernels}(a,aJ)∈kerh相反,假设ker fkerh和BC=,则B=或C =。 当B =时,设g是B C中唯一的函数 ;注意g是“空函数”,因此g f也是空的。此外,由于f的类型,所以h也是空的,因此等于gf。当C=时,我们通过gb = ha来定义b在f的范围内对于fa=b的a;这是一个正确的定义,因为如果有两个选择a,aJ,fa= faJ=b,那么ha = haJ也是假设的。 对于b在f的范围之外,我们任意定义gb。 通过构造,这给出了对于每个a,ha=g(f a)。✷我们还使用以下关于初始代数的简单事实:引理4.4µF→A/=µF A→A/=µ证据 我们注意到F A A=等价于A=F A=,其含义可以验证如下:A=0{µF→A/=µF=µ{in:F(µF)→µF}F(µF)=100{µF=F A=F✷定理4.2的证明给出了上面的两个引理,定理的证明几乎是极其简单的:吉本斯,赫顿和阿滕基希153→→ ×→✷A:F A → A。 h=倍数g优惠 {universal property}A:F A → A。 h·in = g·F h惠{Lemma 4.3}ker(F h)ker(h·in)FA→A/=惠{引理4.4,h:µF→A}kr(F h)kr(h·in)注4.5对于有限列表的类型List(A),其中元素来自A,构造函数nil:1List(A)和cons:A List(A)List(A),定理4.2简化为说明任意函数h:List(A)B可以直接写为折叠,当h标识的列表在cons下闭合时,在这个意义上,对于所有x,xs,ys,h xs=hysh(x:xs)=h(x:ys)吉本斯,赫顿和阿滕基希154→/→··→→→ ×→例4.6如果我们定义sum:List(N)→N,sum[]=0sum(x:xs)= x+sum xs那么很容易证明由sum标识的列表在cons下是封闭的:sum(x:xs)=sum(x:ys)优惠 {和的定义}x+sum xs=x+sum ys替代 性(Substitution)sum xs=sum ys因此,sum可以直接使用fold来写。例4.7相反,如果我们定义一个函数stail:List(N)List(N)(表示'safetail'),stail[]=[stail(x:xs)= xs然后一个简单的反例验证了stail所标识的列表在cons下不是封闭的:例如,对于xs=[ ]和ys=0:[ ],我们有stail xs=[ ]=stail ys,但stail(1:xs)=[ ]= 0:[ ]=stail(1:ys)。因此stail不能直接写成fold。例4.8对于有限实数列表的类型List(R),考虑计算floorsum = floorrsum的问题,其中rsum:List(R)R对实数列表求和,floor:RZ将实数r向下舍入为最大整数,莫斯特河因为结果是一个整数,人们可能会想,floorsum是否可以作为整数的折叠来执行,从而避免计算上更昂贵的实数运算。它不能:我们有floorsum(0。3:[ ])= floorsum(0.6:[]),但是floorsum(0. 五比零3:[])/= floorsum(0. 五比零6:[])。另一方面,反向合成求和映射地板,在求和之前覆盖列表的每个元素,可以写为折叠:类似于例4.6的这是森林砍伐的一个实例[24],一种优化,将两个计算合并为一个,并消除中间数据结构(这里是List(Z)类型)。注4.9对于具有构造函数叶子的二叉树的类型Tree(A):一树(A)和节点:树(A)树(A)树(A),定理4.2简化为说明任意函数h:树(A)B可以直接写作为一个折叠,当被h标识的树在节点下闭合时,在这个意义上,对于所有的t,u,h t=h tJ<$h u=h uJ<$h(node(t,u))=h(node(tJ,uJ))吉本斯,赫顿和阿滕基希155→·→→×→例4.10对于另一个森林砍伐的例子,考虑flatsum=sum flatten,其中flatten:Tree(A)List(A)生成树的元素列表。可以消除flatsum中的中间列表,因为flatsum(node(t,u))(二)平和(flatsum)sum(flatten(node(t,u)【解析】【解析】【解析】sum(flattent++flattenu)={sumdistributedoverver++}sum(flatten t)+sum(flatten u)(二)平和(flatsum)平和t+平和u由此我们得出结论,在flatsum下识别的树在北东。(这里,“+ +”连接两个列表。)例4.11谓词bal:树(A)B持有树i,它是平衡的(所有的叶子都在同一深度),它不是一个折叠:树t是平衡的,深度为1,树u是平衡的,深度为2,t和u都被bal标识(都产生真),但是bal(node(t,t))/= bal(node(t,u))。例4.12然而,函数dbal:Tree(A)NB计算一个对,树的深度和它是否平衡,是一个折叠。因为depth(node(t,u))=1+max(depth t,depth u)bal(node(t,u))= baltbalu深度t=深度u由dbal标识的树在节点下闭合。这是一个多同态[7]或几乎同态[3,8]的例子;将一个函数转换成这样的形式是构建一个高效的数据并行算法来计算它的重要一步。5什么时候一个函数是展开的?对偶定理4.2展开是简单的。函数的核的概念的适当对偶是它的图像:定义5.1函数f:A的图像B是元素的集合由f:imgf= {b ∈ B |a∈ A。fa = b吉本斯,赫顿和阿滕基希156·∈∈∈·/公司简介联系我们内核和图像之间的二元性可能不是立即明显的,但通过关系思维揭示。特别地,如果函数以显而易见的方式被视为关系,则函数f与其逆函数f的关系复合f f恰好是f的核,而对偶复合f·f是f的像(关于f的恒等关系)。现在我们可以给出我们关于unfold的结果,它给出了一个充要条件,即当SET中的任意箭头h:A→νF对于另一个箭头g:A→F A可以写成h=unfoldg的形式时。定理5.2设h:A → νF。然后(图:A→ F A。h =展开g)惠img(F h)展开img(out·h)(另一种说法是,h是一个展开i,即imgh是out的一个不变量;也就是说,将Pred(F)(P)写为谓词提升到A上的谓词P在F A上的一个谓词,i <$Pred(F)(∈imgh)(outx)从(∈imgh)x得出。证明的关键是引理4.3的对偶,即包含图像等价于“预因子”的存在引理5.3假设f:B → C和h:A → C。然后(图:A→B。 h=f·g)惠(imgfimghA→B/=)证据对于n方向,假设g:A→B和h=f·g;那么显然A→B/=n,更进一步,c∈imgh优惠 {image}你好 ha = cfit{h=f·g}你好 f(g a)= c{g:A→B}B.fb=c优惠 {image}c∈imgf相反,假设imgfimgh且A B=,则A=或B=。 当A=时,则h是空函数;让g也是空函数,所以fg也是空的,因此等于h。 当B= 时,我们对A定义ga如下。 设c=h a;通过假设,cimgf也是,所以存在b B,其中fb=c,我们定义ga为这样的ab。如果这样最好的不止一个,我们选择哪一个都无所谓。通过构造,这给出了对于每个a,ha=f(g a)。✷吉本斯,赫顿和阿滕基希157→/→→→我们也使用引理4.4的对偶:引理5.4A→νF/=πA→F A/=π证据我们注意到A F A=等价于A=F A=,其含义可以通过结合两个计算来验证:A/=0{A→νF/=νF/=π{out:v F→F(v F)}F(νF)/=π和A/=0联系 我们νF→A/=π函数{functors}F(νF)→FA=/π也就是说,A/=意味着F(νF)/=和F(νF)→FA/=,这反过来又意味着FA/=。✷定理5.2的证明同样简单:A→ F A。h=展开g优惠 {universal property}A→ F A。输出·h = F h·g惠{Lemma 5.3}img(F h)img(out·h)A→F A/=惠{引理5.4,h:A→νF}img(F h)±img(out·h)✷注5.5对于从A中提取元素的流类型Stream(A),其析构函数head:Stream(A)A和tail:Stream(A)Stream(A),定理5.2简化为说明任意函数h:B Stream(A)可以精确地直接写成展开,当h可产生的每个流的尾部本身也可由h产生时,在这个意义上:img(tail·h)≠ imgh。吉本斯,赫顿和阿滕基希158→→→··→·×·→××→{|∈}·{|∈}·→S例5.6考虑在2.2节中定义的函数from:NStream(N)。 然后(tailfrom)n是流[n+1,n+2,. . ],并且通常,img(tail from)是流[n+1,n+2,.. . nN, 这包括在imgfrom中,流集合[n,n+1,.. . nN .因此,可以直接使用unfold来编写from。例5.7相反,如果我们定义一个函数mults:NStream(N),使得mults n产生多个[0,n,n2,n 3,. . ]的自然n,则(tail mults)n是流[n,n 2,. . ],因此img(tail mults)不包括在imgmults中,后者只包括头部为0的流。因此多边形不能直接写成展开。注5.8对于无限二叉树的类型CoTree(A),其元素取自A,析构函数root:CoTree(A)A和left,right:CoTree(A)CoTree(A),定理5.2简化为说明任意函数h:B CoTree(A)可以精确地写为展开,当每个可由h产生的树的左和右本身也可由h产生时:img(左·h)imghimg(右·h)imgh例5.9考虑无限二叉树,每个节点都用路径标记,这是一个有限的布尔列表,记录了从根节点到该节点的左转弯和右转弯。 生成此树的函数paths:1CoTree(List(B))不是一个展开,因为img(leftpaths)和img(right paths)包含的树在其根处具有单例列表,而这些单例列表不包括在img paths中,img paths包含的树在其根处具有空列表。例5.10相比之下,更通用的函数pathsfrom:List(B)CoTree(List(B))生成从给定路径开始的路径树是一个展开,因为(left·pathsfrom)bs=pathsfrom(false:bs)意味着img(left·pathsfrom)包含在imgpathsfrom中,right也是如此。6结论对于范畴ET的特殊情况,我们已经给出了当任意箭头可以直接写成折叠或展开时的第一个完整结果。在未来的工作中,我们将研究结果是否可以推广到其他类别,以及其他递归模式,例如原始(共同)递归[19,22]和价值路线(共同)迭代[23]。以及从理论的角度来看是有趣的,我们也期望的结果有实际应用程序优化。一个结构良好的程序通常被分解为几个阶段,每个阶段生成一个数据结构,该数据结构被后续阶段使用;deforestation[9,16,21]融合相邻阶段并消除中间数据结构。当作为编译器优化执行时,它会产生有效的观测结果。吉本斯,赫顿和阿滕基希159在不牺牲源代码的结构和清晰度的情况下执行代码。我们的结果可以用来确定何时两相不能融合成一个褶皱或展开。也许可以使用自动测试系统,如QuickCheck [2]来找到适当夹杂物的反例。确认我们非常感谢Lambert Meertens,他的建议大大简化了我们的证明。我们也感谢匿名推荐人提供的有用意见。Graham Hutton获得了EPSRC的结构化递归编程资助,并与Thorsten Altenkirch一起获得了ESPRIT应用语义工作组的支持。引用[1]R. Bird和O.德·摩尔 程序设计的代数。 Prentice Hall,1997年。[2]K. Claessen和J. 休斯Quickcheck:一个轻量级工具,用于随机测试Haskell程序。第五届ACM SIGPLAN函数式编程国际会议,2000年9月。[3]M.科尔列表同态的并行编程。并行处理信件,5(2):191[4]杰·吉本斯计算函数程序。在暑期学校和讲习班的代数和coalgebraic方法在数学的程序建设,牛津,2000年4月。[5]J. Gibbons和G.赫顿结构化共递归程序的证明方法第一届苏格兰函数式编程研讨会,斯特灵,苏格兰,1999年8月。[6]吉本斯和G.琼斯被低估的发展。第三届ACM SIGPLAN国际函数编程会议,马里兰州巴尔的摩,1998年9月。[7]M. M.该死法律与秩序在中国特温特大学博士论文,1992年。[8]S.戈拉奇并行程序开发中列表同态的提取与实现。计算机程序设计科学,33:1[9]Z. Hu,H. Iwasaki和M.武一从递归定义导出结构性质同态。 在proc 第一届ACM SIGPLAN函数式编程国际会议,1996年。[10]G.赫顿程序语义的折叠和展开。第三届ACM SIGPLAN国际函数编程会议,马里兰州巴尔的摩,1998年9月。吉本斯,赫顿和阿滕基希160[11]G.赫顿 关于折叠的普遍性和表现力的教程。 杂志,9(4):355 -372,1999年7月。[12]B.雅各布斯在coalgebraic specification练习。在暑期学校和讲习班的代数和coalgebraic方法在数学的程序建设,牛津,2000年4月。[13]B.雅各布斯湖Moss,H. Reichel,andJ. Rutten,editors. 第一届计算机科学中的共代数方法研讨会论文集。Elsevier Science B.V.,1998.电子笔记在理论计算机科学卷11。[14]B. Jacobs和J. 拉顿关于(余)代数和(余)归纳法的教程Bulletin of theEuropean Association for Theoretical Computer Science,62:222-259,1997.[15]B. Jacobs和J.Rutten,编辑。第二届计算机科学中的共代数方法研讨会论文集。Elsevier Science B.V.,1999.电子笔记在理论计算机科学卷19。[16]J. Launchbury和T.谢尔德Warm fusion:从递归定义中推导构建catas函数式编程语言和计算机体系结构会议,ACM出版社,1995年。[17]S.麦克·莱恩工作数学家的分类。 数学研究生教材Springer-Verlag,1971.[18]G.马尔科姆代数数据类型和程序转换。Science of Computer Programming,14(2-3):255[19]L.米尔滕斯变态。Formal Aspects of Computing,4(5):413-424,1992.[20]E. Meijer,M. Fokkinga和R.帕特森用香蕉、镜头、信封和铁丝网进行函数式编程。在J.Hughes,编辑,Proc. Conference on Functional Programmingand Computer Architecture,LNCS第523号。Springer-Verlag,1991.[21]A. Takano和E.梅杰以计算的形式计算森林砍伐函数式编程语言和计算机体系结构,ACM出版社,1995年。[22]V. Vene和T.乌斯塔鲁函数式程序设计与同构(协递归)。爱沙尼亚科学院学报:物理学,数学,47(3):147[23]维内归纳型和共归纳型的分类规划。塔尔图大学博士论文,2000年。[24]P. Wadler砍伐森林:改变计划以消灭树木。理论计算机科学,73:231
下载后可阅读完整内容,剩余1页未读,立即下载
cpongm
- 粉丝: 5
- 资源: 2万+
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 探索数据转换实验平台在设备装置中的应用
- 使用git-log-to-tikz.py将Git日志转换为TIKZ图形
- 小栗子源码2.9.3版本发布
- 使用Tinder-Hack-Client实现Tinder API交互
- Android Studio新模板:个性化Material Design导航抽屉
- React API分页模块:数据获取与页面管理
- C语言实现顺序表的动态分配方法
- 光催化分解水产氢固溶体催化剂制备技术揭秘
- VS2013环境下tinyxml库的32位与64位编译指南
- 网易云歌词情感分析系统实现与架构
- React应用展示GitHub用户详细信息及项目分析
- LayUI2.1.6帮助文档API功能详解
- 全栈开发实现的chatgpt应用可打包小程序/H5/App
- C++实现顺序表的动态内存分配技术
- Java制作水果格斗游戏:策略与随机性的结合
- 基于若依框架的后台管理系统开发实例解析
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功