我想实现我的自定义函数minWithIndex,所以我研究了scala library中的 zipWithIndex 函数。 我无法理解。
我知道函数的功能,但是无法弄清楚它是如何执行的。 预先感谢
def zipWithIndex[A1 >: A, That](implicit bf: CanBuildFrom[Repr, (A1, Int), That]): That = {
val b = bf(repr)
var i = 0
for (x <- this) {
b += ((x, i))
i += 1
}
b.result()
}
答案 0 :(得分:1)
您的问题实际上看起来像是两个不同的问题:
Scala标准库集合的设计方式,尤其是zipWithIndex
的工作方式?
如何实现我的自定义minWithIndex
?
不幸的是,第一个问题可能比第二个问题复杂。
从2.12版开始,当前的scala.collection
的设计非常灵活,但要付出代价,要使用许多高级功能和/或设计模式。还有人可能会争辩说,它在实际使用中是过度设计的,因此对于Scala新手来说很难理解。
设计馆藏库是一个众所周知的难题,因为理想情况下,您希望捕获许多不同的方面。主要的事情是您可能希望捕获尽可能多的方面,但同时又要尽可能减少代码重复。 (例如,考虑链接列表与基于数组的列表。两者都应具有可以像indexOf
和size
那样实现的getByIndex
之类的方法,因此理想情况下,您希望拥有这里只有一个实现。)这是一个真正的设计挑战。
以下是影响Scala的馆藏设计的各个方面的详尽列表(您可能还会在早期(2.8)版本here上找到一些设计说明):
用于实现相似功能的不同数据结构的统一接口(例如,链表与基于数组的列表非常相似,而链表与基于树的集合则不太相似,但仍具有一些共同点)。这就是Seq
或GenTraversableLike
等各种特征和深层继承层次的原因。
类型安全。我们希望整数列表与字符串列表的类型不同。而且我们想知道存储元素的类型。这就是为什么所有集合都是通用的。
性能。在每种语言中,标准集合都是最常用的代码之一,因此良好的性能非常重要。这就是为什么在几个地方对于纯不可变FP接口存在“不纯”可变实现的原因。
单独的只读和可变集合(在FP语言中特别重要,在其他语言中也特别重要)。这就是为什么有scala.collection
,scala.collection.mutable
和scala.collection.immutable
软件包的原因。
支持潜在的无限序列,例如生成的(斐波那契数)或来自外部源的序列(例如,从控制台输入读取的字节序列)。这就是诸如TraversableOnce
或Iterable
之类的原因。
支持无法(轻松)处理两次的序列。例如,某个主要证券交易所上的所有交易流是如此之大,以至于无法存储和重新处理。这就是TraversableOnce
与Traversable
内部与外部迭代(这是Iterable
与Traversable
之间分离的原因)
急速计算与惰性计算(这是“ View”后缀类型的原因)
支持不是集合的类似集合的对象。例如,您正在实现HTML解析器甚至浏览器。许多HTML元素可以有子级,但是成为子级的集合并不是主要责任。这就是带有'Gen`前缀的各种特征的原因。
现在让我们回到zipWithIndex
。这是许多方法中的一种(其他类似方法是map
,fitler
和其他方法),这对设计人员构成了额外的挑战:这些方法从当前方法中产生了新的收藏。一方面,这样的方法可以一次通用地用于所有集合,另一方面,如果使用朴素的实现,我们将丢失特定的类型,更糟糕的是,我们可能被迫更改实现,从而可能会更改语义。考虑一下filter
方法的这种天真的实现:
trait Iterable[A] {
def filter(p: A => Boolean): ? = {
var res: List[A] = Nil
for (x <- this)
if (p(x)) res = x :: res
res
}
}
要放置哪种返回类型而不是?
?如果仅放置Iterable[A]
,则意味着我们立即失去了使用List
中不可用的Iterable
的所有方法的能力(例如按索引访问)。但是另一件事是,如果我们的Iterable
实际上是Set
,该怎么办。我们可能希望filter
的结果再次成为Set
,而不是List
。这就是上面的参考文献所称的“ 相同结果类型原理 ”。 Scala是为数不多的几种语言之一,可让您以最小的代码重复来设计一个优雅的解决方案。主要思想是每个实现都有一个伴随对象(例如Iterable和immutable.List),每个中间特征都具有一个伴随特征(例如SeqFactory)。这些同伴提供的功能是可以创建“相同”集合的 new 实例(可能具有不同的通用类型)。
所引用的文章中最后没有提到的是CanBuildFrom
隐式参数。其背后的逻辑如下:假设您有一个链表(List
),并希望将带有索引的基于数组的列表(Vector
)压缩。您是否需要在此过程中创建带有索引的中间链接列表? CanBuildFrom
的诀窍是:不,您不必这样做。隐式参数是Scala的一项相当高级的功能,如果您还不熟悉,则可能应该阅读更多内容。这个想法是编译器可以自动搜索匹配类型的参数。因此,如果有任何匹配类型的值是作用域,则代码将进行编译。这意味着CanBuildFrom
的存在可以用作证据,您可以在进行某些数据操作的同时更改集合的基础数据结构(zipWithIndex
, map
,filter
等)。每个伴随对象都提供具有相同目标数据结构的默认CanBuildFrom
值(例如,参见immutable.List.canBuildFrom)。
因此,现在我们可以了解如何实现IterableLike.zipWithIndex
(这是所有子类型的默认实现)
def zipWithIndex[A1 >: A, That](implicit bf: CanBuildFrom[Repr, (A1, Int), That]): That = {
val b = bf(repr)
var i = 0
for (x <- this) {
b += ((x, i))
i += 1
}
b.result()
}
签名表明,使用zipWithIndex
可以将Repr
的集合A
转换成That
-元组(A1, Int)
的集合,只要>
A1
是A
的超类型(这是安全的上流)That
(Repr
对象)构建bf
该方法的作用是要求bf
证据创建b
-一个新的Builder[(A1, Int), That]
,然后用元组填充生成器,最后返回That
从构建器(b.result()
中收集。出于性能原因,同时使用了builder和带有for
的{{1}}。
答案取决于您希望它有多通用,以及多少性能对您很重要。以下是一些选项:
var i
请注意,此处使用implicit class MinWithIndexOps[A, Repr <: IterableLike[A, Repr]](it: IterableLike[A, Repr]) {
def minWithIndexWithZip[B >: A](implicit cmp: Ordering[B], bf: CanBuildFrom[Repr, (A, Int), Iterable[(A, Int)]]): (A, Int) = it.zipWithIndex(bf).min(Ordering.by[(A, Int), B]((kv: (A, Int)) => kv._1))
def minWithIndexWithFold[B >: A](implicit cmp: Ordering[B]): (A, Int) = {
if (it.isEmpty)
throw new UnsupportedOperationException("empty.min")
val h = it.head
val res = it.foldLeft((h, 0, 0))((acc, cur) => acc match {
case (minVal, minIndex, curIndex) => if (cmp.lteq(minVal, cur)) (minVal, minIndex, curIndex + 1) else (cur, curIndex, curIndex + 1)
})
(res._1, res._2)
}
def minWithIndexWithVars[B >: A](implicit cmp: Ordering[B]): (A, Int) = {
if (it.isEmpty)
throw new UnsupportedOperationException("empty.min")
var minVal = it.head
var minIndex = 0
var i = 0
for (cur <- it) {
if (cmp.gt(minVal, cur)) {
minVal = cur
minIndex = i
}
i += 1
}
(minVal, minIndex)
}
}
def test(): Unit = {
val data = "qwerty" :: "asdfg" :: "zxcvb" :: Nil
println(data.minWithIndexWithZip)
println(data.minWithIndexWithFold)
println(data.minWithIndexWithVars)
}
关键字以不同的含义来创建一个implicit
,该关键字可以使用新方法有效地扩展implicit class
的每个实例。
第一个实现IterableLike
非常简单,但效率可能很低:您实际上是先执行minWithIndexWithZip
然后调用zipWithIndex
(另请注意,标准min
在抛出异常而不返回min
的Scala约定,我在其他实现中使用了相同的语义)。这种方法效率低下,因为您必须创建整个新集合才能将其立即处置。
Option[A]
是不依赖于minWithIndexWithFold
而使用zipWithIndex
的另一种实现。 foldLeft
同时是一个非常基本且非常通用的操作。该代码的一个好处是它也是不可变的。但是由于这个原因,它的性能也不佳:将立即创建并处置许多中间累加器对象。
最后一个fold
可能是最“纯”的,但性能最高的版本,它使用简单的类似命令式的代码。
答案 1 :(得分:0)
我看到scala有时在内部使用可变解决方案。与 import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:share/share.dart';
class Everything extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return _EverythingState();
}
}
class _EverythingState extends State<Everything> {
Widget _buildEventCards(BuildContext context, DocumentSnapshot document) {
var width = MediaQuery.of(context).size.width;
return Container(
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 14.0),
child: Card(
elevation: 0.0,
child: Column(
children: <Widget>[
Image.asset(
document['image'],
fit: BoxFit.cover,
),
Container(
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0),
child: Column(
children: <Widget>[
SizedBox(
height: 15.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
// Icon(document['icon']),
],
),
],
),
SizedBox(
height: 10.0,
),
Wrap(
direction: Axis.horizontal,
alignment: WrapAlignment.start,
runAlignment: WrapAlignment.start,
children: <Widget>[
Chip(
backgroundColor: Colors.grey[100],
label: Text(
document['date'],
),
),
SizedBox(
width: 10.0,
),
Chip(
backgroundColor: Colors.grey[100],
label: Text(document['source']),
),
SizedBox(
width: 10.0,
),
Chip(
backgroundColor: Colors.grey[100],
label: Text(
document['location'],
),
),
],
),
SizedBox(
height: 15.0,
),
Container(
width: width,
child: Text(
document['title'],
style: TextStyle(fontSize: 24.0),
),
),
SizedBox(
height: 20.0,
),
Text(document['desc']),
SizedBox(
height: 20.0,
),
Row(
children: <Widget>[
FlatButton(
child: Text(
'More',
style: TextStyle(
color: Color.fromRGBO(118, 190, 208, 1.0)),
),
color: Colors.grey[100],
onPressed: () {
print('webview clicked');
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => WebviewScaffold(
url: document['url'],
appBar: AppBar(
title: Text(document['source']),
backgroundColor: Color.fromRGBO(
135,
142,
136,
1.0,
),
),
),
),
);
},
),
SizedBox(
width: 15.0,
),
new MyButton(),
],
),
],
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Firestore.instance.collection('stories').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_buildEventCards(context, snapshot.data.documents[index]),
);
});
}
// @override
// void initState() {
// // TODO: implement initState
// super.initState();
// FirebaseAdMob.instance.initialize(appId: FirebaseAdMob.testAppId);
// var bannerAd = _buildBanner()..load();
// }
}
class MyButton extends StatelessWidget {
const MyButton({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return FlatButton(
child: Text(
'Share',
style: TextStyle(
color: Color.fromRGBO(245, 93, 62, 1.0)),
),
color: Colors.grey[100],
onPressed: () {
final RenderBox box = context.findRenderObject();
Share.share('Hello this is a test',
sharePositionOrigin:
box.localToGlobal(Offset.zero) &
box.size);
},
);
}
}
的实现相同。
基本上是:
zipWithIndex
循环)for
和(a, Int)
)因此,对于输入b += ((x, i))
,您会在List("first", "second")
之后得到List(("first", 1), ("second", 2))
。
zipWithIndex
您可以使用具有不变性的递归方法来解决相同的问题
scala> List("first", "second").zipWithIndex
res3: List[(String, Int)] = List((first,0), (second,1))