南昌网站开发公司电话/杭州seo公司排名
编程编程练习网站
上周,我们第一次体验了编程风格的练习。 请记住,目标是编写一个简单的程序,但要遵守一些约束。 先前的限制是只有一个变量可用,即数组。 对于像Kotlin这样的静态类型的语言,在使用它们之前,需要将大量的变量强制转换为正确的类型。
本周,约束条件是极端的,但有所不同。 除了两个数组,我们还有两个可用的数据结构:
- 哈希图-也称为字典或关联数组-称为堆
- 一个恰当地命名为...的堆栈
目标是在堆栈上执行所有操作,并且仅在必要时在堆上存储数据。 幸运的是,我曾经参加过关于非主流语言的主题演讲,包括基于堆栈的语言PostScript。 因此,我对基于堆栈的语言如何工作有些熟悉。 但是,如果您对它们一无所知,我相信这篇文章将会很有趣。
这是第2 次后,在编程风格焦点series.Other职位的练习包括:
- 以编程风格介绍练习
- 以编程风格进行练习,将内容堆叠起来 (本文)
- 编程风格的练习,Kwisatz Haderach风格
- 编程风格的练习,递归
- 具有高阶功能的编程风格的练习
- 以编程风格进行练习
- 以编程风格进行练习,回到面向对象的编程
- 编程风格的练习:地图也是对象
- 编程风格的练习:事件驱动的编程
- 编程风格的练习和事件总线
- 反思编程风格的练习
- 面向方面的编程风格的练习
- 编程风格的练习:FP&I / O
- 关系数据库风格的练习
- 编程风格的练习:电子表格
- 并发编程风格的练习
- 编程风格的练习:在线程之间共享数据
- 使用Hazelcast以编程风格进行练习
- MapReduce风格的练习
- 编程风格的练习总结
堆栈和基于堆栈的语言快速入门
我假设您对称为Stack
的数据结构有些熟悉。 这是一个专门的队列,具有先进先出,语义先进的语义。 类似于一堆板:假设将板A放在堆栈上,然后将板B再放在板C上。一个人只能从堆栈中将板放在顶部:因此,顺序将是C,然后是B,然后是一个。
同样,基于堆栈的语言将变量放在堆栈上。 要调用一个函数,首先需要将参数压入堆栈,然后才调用函数。 然后,一个函数将从堆栈中弹出其参数,并将结果压入堆栈。
一个简单的加法将转换为以下伪代码:
PUT 1
PUT 2
ADD
这样,函数ADD
将弹出2
,然后弹出1
,将它们求和并将结果压入堆栈。
在Kotlin中,这可能会翻译为以下内容:
stack.push(1)
stack.push(2)
stack.push((stack.pop()asInt)+(stack.pop()asInt)
)
可以创建一个add()
函数:
funadd()=stack.push((stack.pop()asInt)+(stack.pop()asInt)
)stack.push(1)
stack.push(2)
add()
练习中的堆栈
程序的开头是一个很好的示例,可以理解如何超越上述简单示例:
funrun(filename:String):Map<*,*>{stack.push(filename) (1)readFile()filterChars()...
}funreadFile(){valf=read(stack.pop()asString) (2)stack.push(f) (3)
}
- 将作为参数传递的文件名压入堆栈
- 弹出文件名并读取文件内容
- 将文件行推入堆栈
read()
函数将文件名作为参数,因为它是所有章节之间共享的共享实用程序函数。 如果要完全遵守约束,则应将其更改为不接受任何参数,并从堆栈中获取文件名。 其他功能不带参数,并严格遵守规则。
funfilterChars(){stack.push( (1)(stack.pop()asList<*>) (2).map{"\\W|_".toRegex().replace(itasString," ")} (3).map(String::toLowerCase)) (3)
}
- 将处理结果压入堆栈
- 弹出文本行作为
List
进行处理 - 处理字符串
现在,此功能几乎完全符合锻炼的精神。
为下一步做准备
实际上,以前的代码在我看来有点作弊: map()
是Kotlin的stdlib中的函数,应在堆栈上实现。
这需要两个可以通过Kotlin扩展功能实现的帮助程序功能:
- 一个
extend()
方法(类似于Python),它获取一个集合,并将其所有元素压入堆栈:fun<T>Stack<T>.extend(iterable:Iterable<T>){iterable.forEach{push(it)} }
- 一个
swap()
方法,用于交换堆栈中前两个元素的位置:funStack<Any>.swap(){vala=pop() (1)valb=pop() (1)push(a)push(b) }
- 通常应禁止使用局部变量,而应使用堆。 但是,我允许自己使用这个小捷径,因为它看起来更好,并且变化不大。
整个九码
最简单的部分是通过用extend()
替换push()
来更新readFile()
函数的实现:
funreadFile(){valf=read(stack.pop()asString)stack.extend(f) (1)
}
- 此时,堆栈应在读取文件中每行文本包含一个元素
下一步需要从堆栈中弹出每个字符串,对其进行处理,然后将其推回。 但是,由于堆栈的性质,处理后的字符串将放回顶部。 并且只能访问顶部字符串。 我们可以使用堆而不是将字符串推回堆栈上,但这会作弊...
完全符合堆栈要求的替代方法如下:
- 将可变列表压入堆栈
- 重复以下步骤,直到堆栈上只有一个元素
- 交换前两个元素-列表和字符串
- 弹出第一个元素-这将是要处理的字符串
- 处理字符串
- 弹出现在的第一个元素-这将是列表
- 将处理后的字符串添加到列表中
- 将列表推回堆栈
- 最后,弹出包含所有已处理字符串的列表
- 并将其内容作为单独的字符串压入堆栈
还有另外一招。 要将元素添加到可变列表,API为list.add(string)
。 在堆栈上,这将转换为stack.pop().add(stack.pop())
。 这意味着列表应该是堆栈中的第一项。 但是,我们过程的顺序相反:第一个项目应该是字符串,第二个应该是列表。 因此,我们需要一个函数来反转调用者(列表)和被调用者(字符串)的顺序。 这可以通过另一个扩展功能轻松完成:
fun<T>T.addTo(collection:MutableCollection<T>)=collection.also{it.add(this)}
相关的实现是:
funfilterChars(){stack.push(mutableListOf<String>())while(stack.size>1){stack.swap()stack.push("\\W|_".toRegex().replace((stack.pop()asString)," ").toLowerCase().addTo(stack.pop()asMutableList<Any>))}stack.extend(stack.pop()asMutableList<Any>)
}
将一个集合压入堆栈,交换,迭代弹出直到堆栈上只剩下一个元素,然后将结果压入堆栈的技巧,因为单个项目可以在其他函数中复制。 它仅需要其他扩展功能,具体取决于集合的类型, 例如 MutableMap
。
其他注意事项
还有一些其他注意事项值得一提。
评价
评估堆栈中一项的第一种方法是将其弹出,将其存储在堆中,然后在必要时将其再次推回。 可以通过使用peek()
方法来避免这种情况: peek()
允许获取对堆栈中第一个项目的引用,但不会从堆栈中弹出它-它仍然存在。
if((stack.peek()asString).length<2){// Do something
}
过滤
评估用于过滤项目。 要从堆栈中丢弃物品,只需将其弹出...就可以了。
if((stack.peek()asString).length<2)stack.pop() (1)
else(heap["words"]asMutableList<Any>).add(stack.pop()) (2)
- 筛选出项目
- 将项目存储在堆上
自定义堆栈实现
本章的另一项任务是创建自己的堆栈实现。 JDK中提供的默认Stack
类存在一些问题:
- 它继承自
AbsractList
,从而实现List
! 这意味着,尽管它提供了特定于堆栈的方法,例如pop()
和push()
,但它也从整个继承层次结构中泄漏了许多无关的方法,例如add()
和remove()
。 尽管我们的代码没有使用那些不相关的方法,但这仍然是一个糟糕的设计决策。 - 它的父类是
Vector
。 虽然每本身不会被弃用,这类下跌的青睐,因为在其使用的synchronized
关键字。 从纯粹的性能角度来看,最好使用标准的ArrayList
。 如果需要线程安全,则可以在其周围使用Collections.synchronizedList()
。
由于设计和性能这两个原因,很少使用默认的Stack
实现。
从设计的角度来看,自定义堆栈的合同非常简单:
interfaceStack<T>{valsize:Intfunpush(t:T?)funpop():Tfunpeek():TfunisNotEmpty()
}
从性能的角度看,看起来链表实现就足够了。 有没有搜索,仅添加/移除的第一个元素:在链接列表中的搜索是O(n),而添加和删除所述第一元件只O(1)。 JDK API提供了一个链接列表的实现,恰当地命名为LinkedList
。 使用它作为我们自定义堆栈合同中的委托是一件容易的事:
classStack<T>{privatevallist=LinkedList<T>()valsize:Intget()=list.sizefunpush(t:T?)=list.addFirst(t)funpop():T=list.removeFirst()funpeek():T=list.peekFirst()funisNotEmpty()=list.isNotEmpty()
}
结论
与上周的单个数组约束相比,代码库中需要的强制转换更少。
另一方面,堆栈机制要花一些时间来习惯,特别是如果要避免使用堆时:相关的心理健美操很有趣,尤其是交换。
更进一步:
- 面向堆栈的编程
- 通用数据结构操作
翻译自: https://blog.frankel.ch/exercises-programming-style/2/
编程编程练习网站