细说ReactiveCocoa的冷信号与热信号(三):怎么处理冷信号与热信号

第一篇文章中我们介绍了冷信号与热信号的概念,前一篇文章我们也讨论了为什么要区分冷信号与热信号,下面我会先为大家揭晓热信号的本质,再给出冷信号转换成热信号的方法。

揭示热信号的本质

在ReactiveCocoa中,究竟什么才是热信号呢?冷信号是比较常见的,map一下就会得到一个冷信号。但在RAC中,好像并没有“hot signal”这个单独的说法。原来在RAC的世界中,所有的热信号都属于一个类——RACSubject。接下来我们来看看究竟它为什么这么“神奇”。

在RAC2.5文档的框架概述中,有着这样一段描述:

A subject, represented by the RACSubject class, is a signal that can be manually controlled.

Subjects can be thought of as the “mutable” variant of a signal, much like NSMutableArray is for NSArray. They are extremely useful for bridging non-RAC code into the world of signals.

For example, instead of handling application logic in block callbacks, the blocks can simply send events to a shared subject instead. The subject can then be returned as a RACSignal, hiding the implementation detail of the callbacks.

Some subjects offer additional behaviors as well. In particular, RACReplaySubject can be used to buffer events for future subscribers, like when a network request finishes before anything is ready to handle the result.

从这段描述中,我们可以发现Subject具备如下三个特点:

  1. Subject是“可变”的。
  2. Subject是非RAC到RAC的一个桥梁。
  3. Subject可以附加行为,例如RACReplaySubject具备为未来订阅者缓冲事件的能力。

从第三个特点来看,Subject具备为未来订阅者缓冲事件的能力,那也就说明它是自身是有状态的。根据上文的介绍,Subject是符合热信号的特点的。为了验证它,我们再来做个简单实验:

    RACSubject *subject = [RACSubject subject];RACSubject *replaySubject = [RACReplaySubject subject];[[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{// Subscriber 1[subject subscribeNext:^(id x) {NSLog(@"Subscriber 1 get a next value: %@ from subject", x);}];[replaySubject subscribeNext:^(id x) {NSLog(@"Subscriber 1 get a next value: %@ from replay subject", x);}];// Subscriber 2[subject subscribeNext:^(id x) {NSLog(@"Subscriber 2 get a next value: %@ from subject", x);}];[replaySubject subscribeNext:^(id x) {NSLog(@"Subscriber 2 get a next value: %@ from replay subject", x);}];}];[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{[subject sendNext:@"send package 1"];[replaySubject sendNext:@"send package 1"];}];[[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{// Subscriber 3[subject subscribeNext:^(id x) {NSLog(@"Subscriber 3 get a next value: %@ from subject", x);}];[replaySubject subscribeNext:^(id x) {NSLog(@"Subscriber 3 get a next value: %@ from replay subject", x);}];// Subscriber 4[subject subscribeNext:^(id x) {NSLog(@"Subscriber 4 get a next value: %@ from subject", x);}];[replaySubject subscribeNext:^(id x) {NSLog(@"Subscriber 4 get a next value: %@ from replay subject", x);}];}];[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{[subject sendNext:@"send package 2"];[replaySubject sendNext:@"send package 2"];}];

按照时间线来解读一下上述代码:

  1. 0s时创建subjectreplaySubject这两个subject。
  2. 0.1s时Subscriber 1分别订阅了subjectreplaySubject
  3. 0.1s时Subscriber 2也分别订阅了subjectreplaySubject
  4. 1s时分别向subjectreplaySubject发送了"send package 1"这个字符串作为
  5. 1.1s时Subscriber 3分别订阅了subjectreplaySubject
  6. 1.1s时Subscriber 4也分别订阅了subjectreplaySubject
  7. 2s时再分别向subjectreplaySubject发送了"send package 2"这个字符串作为

接下来看一下输出的结果:

2015-09-28 13:35:22.855 RACDemos[13646:1269269] Start
2015-09-28 13:35:23.856 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 1 from subject
2015-09-28 13:35:23.856 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 1 from subject
2015-09-28 13:35:23.857 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 1 from replay subject
2015-09-28 13:35:23.857 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 1 from replay subject
2015-09-28 13:35:24.059 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 1 from replay subject
2015-09-28 13:35:24.059 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 1 from replay subject
2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 2 from subject
2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 2 from subject
2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 2 from subject
2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 2 from subject
2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 2 from replay subject
2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 2 from replay subject
2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 2 from replay subject
2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 2 from replay subject

结合结果可以分析出如下内容:

  1. 22.855s时,测试启动,subjectreplaySubject创建完毕。
  2. 23.856s时,距离启动大约1s后,Subscriber 1Subscriber 2同时subject接收到了"send package 1"这个值。
  3. 23.857s时,也是距离启动大约1s后,Subscriber 1Subscriber 2同时replaySubject接收到了"send package 1"这个值。
  4. 24.059s时,距离启动大约1.2s后,Subscriber 3Subscriber 4同时replaySubject接收到了"send package 1"这个值。注意Subscriber 3Subscriber 4并没有从subject接收"send package 1"这个值。
  5. 25.039s时,距离启动大约2.1s后,Subscriber 1Subscriber 2Subscriber 3Subscriber 4同时subject接收到了"send package 2"这个值。
  6. 25.040s时,距离启动大约2.1s后,Subscriber 1Subscriber 2Subscriber 3Subscriber 4同时replaySubject接收到了"send package 2"这个值。

只关注subject,根据时间线,我们可以得到下图:

Alt text

经过观察不难发现,4个订阅者实际上是共享subject的,一旦这个subject发送了值,当前的订阅者就会同时接收到。由于Subscriber 3Subscriber 4的订阅时间稍晚,所以错过了第一次值的发送。这与冷信号是截然不同的反应。冷信号的图类似下图:

Alt text

对比上面两张图,是不是可以发现,subject类似“直播”,错过了就不再处理。而signal类似“点播”,每次订阅都会从头开始。所以我们有理由认定subject天然就是热信号。

下面再来看看replaySubject,根据时间线,我们能得到另一张图:

Alt text

将图3与图1对比会发现,Subscriber 3Subscriber 4在订阅后马上接收到了“历史值”。对于Subscriber 3Subscriber 4来说,它们只关心“历史的值”而不关心“历史的时间线”,因为实际上12是间隔1s发送的,但是它们接收到的显然不是。举个生动的例子,就好像科幻电影里面主人公穿越时间线后会先把所有的回忆快速闪过再来到现实一样。(见《X战警:逆转未来》、《蝴蝶效应》)所以我们也有理由认定replaySubject天然也是热信号。

看到这里,我们终于揭开了热信号的面纱,结论就是:

  1. RACSubject及其子类是热信号
  2. RACSignal排除RACSubject类以外的是冷信号

如何将一个冷信号转化成热信号——广播

冷信号与热信号的本质区别在于是否保持状态,冷信号的多次订阅是不保持状态的,而热信号的多次订阅可以保持状态。所以一种将冷信号转换为热信号的方法就是,将冷信号订阅,订阅到的每一个时间通过RACSbuject发送出去,其他订阅者只订阅这个RACSubject

观察下面的代码:

    RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {NSLog(@"Cold signal be subscribed.");[[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{[subscriber sendNext:@"A"];}];[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{[subscriber sendNext:@"B"];}];[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{[subscriber sendCompleted];}];return nil;}];RACSubject *subject = [RACSubject subject];NSLog(@"Subject created.");[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{[coldSignal subscribe:subject];}];[subject subscribeNext:^(id x) {NSLog(@"Subscriber 1 recieve value:%@.", x);}];[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{[subject subscribeNext:^(id x) {NSLog(@"Subscriber 2 recieve value:%@.", x);}];

执行顺序是这样的:

  1. 创建一个冷信号:coldSignal。该信号声明了“订阅后1.5秒发送‘A’,3秒发送’B’,5秒发送完成事件”。
  2. 创建一个RACSubject:subject
  3. 在2秒后使用这个subject订阅coldSignal
  4. 立即订阅这个subject
  5. 4秒后订阅这个subject

如果所料不错的话,通过订阅这个subject并不会引起coldSignal重复执行block的内容。我们来看下结果:

2015-09-28 19:36:45.703 RACDemos[14110:1556061] Subject created.
2015-09-28 19:36:47.705 RACDemos[14110:1556061] Cold signal be subscribed.
2015-09-28 19:36:49.331 RACDemos[14110:1556061] Subscriber 1 recieve value:A.
2015-09-28 19:36:50.999 RACDemos[14110:1556061] Subscriber 1 recieve value:B.
2015-09-28 19:36:50.999 RACDemos[14110:1556061] Subscriber 2 recieve value:B.

参考时间线,会得到下图: Alt text

不难发现其中的几个重点: 1. subject是从一开始就创建好的,等到2s后便开始订阅coldSignal。 2. Subscriber 1subject创建后就开始订阅的,但是第一个接收时间与subject接收coldSignal第一个值的时间是一样的。 3. Subscriber 2subject创建4s后开始订阅的,所以只能接收到第二个值。

通过观察可以确定,subject就是coldSignal转化的热信号。所以使用RACSubject来将冷信号转化为热信号是可行的。

当然,使用这种RACSubject来订阅冷信号得到热信号的方式仍有一些小的瑕疵。例如subject的订阅者提前终止了订阅,而subject并不能终止对coldSignal的订阅。(RACDisposable是一个比较大的话题,我计划在其他的文章中详细阐述它,也希望感兴趣的同学自己来理解。)所以在RAC库中对于冷信号转化成热信号有如下标准的封装:

- (RACMulticastConnection *)publish;
- (RACMulticastConnection *)multicast:(RACSubject *)subject;
- (RACSignal *)replay;
- (RACSignal *)replayLast;
- (RACSignal *)replayLazily;

这5个方法中,最为重要的就是- (RACMulticastConnection *)multicast:(RACSubject *)subject;这个方法了,其他几个方法也是间接调用它的。我们来看看它的实现:

/// implementation RACSignal (Operations)
- (RACMulticastConnection *)multicast:(RACSubject *)subject {[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];return connection;
}/// implementation RACMulticastConnection- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {NSCParameterAssert(source != nil);NSCParameterAssert(subject != nil);self = [super init];if (self == nil) return nil;_sourceSignal = source;_serialDisposable = [[RACSerialDisposable alloc] init];_signal = subject;return self;
}#pragma mark Connecting- (RACDisposable *)connect {BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);if (shouldConnect) {self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];}return self.serialDisposable;
}- (RACSignal *)autoconnect {__block volatile int32_t subscriberCount = 0;return [[RACSignalcreateSignal:^(id<RACSubscriber> subscriber) {OSAtomicIncrement32Barrier(&subscriberCount);RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];RACDisposable *connectionDisposable = [self connect];return [RACDisposable disposableWithBlock:^{[subscriptionDisposable dispose];if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {[connectionDisposable dispose];}}];}]setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}

虽然代码比较短但不是很好懂,大概来说明一下:

  1. RACSignal类的实例调用- (RACMulticastConnection *)multicast:(RACSubject *)subject时,以selfsubject作为构造参数创建一个RACMulticastConnection实例。
  2. RACMulticastConnection构造的时候,保存sourcesubject作为成员变量,创建一个RACSerialDisposable对象,用于取消订阅。
  3. RACMulticastConnection类的实例调用- (RACDisposable *)connect这个方法的时候,判断是否是第一次。如果是的话_signal这个成员变量来订阅sourceSignal之后返回self.serialDisposable;否则直接返回self.serialDisposable。这里面订阅sourceSignal是重点。
  4. RACMulticastConnectionsignal只读属性,就是一个热信号,订阅这个热信号就避免了各种副作用的问题。它会在- (RACDisposable *)connect第一次调用后,根据sourceSignal的订阅结果来传递事件。
  5. 想要确保第一次订阅就能成功订阅sourceSignal,可以使用- (RACSignal *)autoconnect这个方法,它保证了第一个订阅者触发sourceSignal的订阅,也保证了当返回的信号所有订阅者都关闭连接后sourceSignal被正确关闭连接。

由于RAC是一个线程安全的框架,所以好奇的同学可以了解下“OSAtomic*”这一系列的原子操作。抛开这些应该不难理解上述代码。

了解源码之后,这个方法的正确使用就清楚了,应该像这样:

	RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {NSLog(@"Cold signal be subscribed.");[[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{[subscriber sendNext:@"A"];}];[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{[subscriber sendNext:@"B"];}];[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{[subscriber sendCompleted];}];return nil;}];RACSubject *subject = [RACSubject subject];NSLog(@"Subject created.");RACMulticastConnection *multicastConnection = [coldSignal multicast:subject];RACSignal *hotSignal = multicastConnection.signal;[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{[multicastConnection connect];}];[hotSignal subscribeNext:^(id x) {NSLog(@"Subscribe 1 recieve value:%@.", x);}];[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{[hotSignal subscribeNext:^(id x) {NSLog(@"Subscribe 2 recieve value:%@.", x);}];}];

或者这样:

    RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {NSLog(@"Cold signal be subscribed.");[[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{[subscriber sendNext:@"A"];}];[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{[subscriber sendNext:@"B"];}];[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{[subscriber sendCompleted];}];return nil;}];RACSubject *subject = [RACSubject subject];NSLog(@"Subject created.");RACMulticastConnection *multicastConnection = [coldSignal multicast:subject];RACSignal *hotSignal = multicastConnection.autoconnect;[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{[hotSignal subscribeNext:^(id x) {NSLog(@"Subscribe 1 recieve value:%@.", x);}];}];[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{[hotSignal subscribeNext:^(id x) {NSLog(@"Subscribe 2 recieve value:%@.", x);}];}];

以上的两种写法和之前用Subject来传递的例子都可以得到相同的结果。

下面再来看看其他几个方法的实现:

/// implementation RACSignal (Operations)
- (RACMulticastConnection *)publish {RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];RACMulticastConnection *connection = [self multicast:subject];return connection;
}- (RACSignal *)replay {RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];RACMulticastConnection *connection = [self multicast:subject];[connection connect];return connection.signal;
}- (RACSignal *)replayLast {RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name];RACMulticastConnection *connection = [self multicast:subject];[connection connect];return connection.signal;
}- (RACSignal *)replayLazily {RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];return [[RACSignaldefer:^{[connection connect];return connection.signal;}]setNameWithFormat:@"[%@] -replayLazily", self.name];
}

这几个方法的实现都相当简单,只是为了简化而封装,具体说明一下:

  1. - (RACMulticastConnection *)publish就是帮忙创建了RACSubject
  2. - (RACSignal *)replay就是用RACReplaySubject来作为subject,并立即执行connect操作,返回connection.signal。其作用是上面提到的replay功能,即后来的订阅者可以收到历史值。
  3. - (RACSignal *)replayLast就是用Capacity为1的RACReplaySubject来替换- (RACSignal *)replay的`subject。其作用是使后来订阅者只收到最后的历史值。
  4. - (RACSignal *)replayLazily- (RACSignal *)replay的区别就是replayLazily会在第一次订阅的时候才订阅sourceSignal

所以,其实本质仍然是

使用一个Subject来订阅原始信号,并让其他订阅者订阅这个Subject,这个Subject就是热信号。

现在再回过来看下之前系列文章第二篇中那个业务场景的例子,其实修改的方法很简单,就是在网络获取的fetchData这个信号后面,增加一个replayLazily变换,就不会出现网络请求重发6次的问题了。

修改后的代码如下,大家可以试试:

self.sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://api.xxxx.com"]];self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];@weakify(self)RACSignal *fetchData = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {@strongify(self)NSURLSessionDataTask *task = [self.sessionManager GET:@"fetchData" parameters:@{@"someParameter": @"someValue"} success:^(NSURLSessionDataTask *task, id responseObject) {[subscriber sendNext:responseObject];[subscriber sendCompleted];} failure:^(NSURLSessionDataTask *task, NSError *error) {[subscriber sendError:error];}];return [RACDisposable disposableWithBlock:^{if (task.state != NSURLSessionTaskStateCompleted) {[task cancel];}}];}] replayLazily];  // modify here!!RACSignal *title = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {if ([value[@"title"] isKindOfClass:[NSString class]]) {return [RACSignal return:value[@"title"]];} else {return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];}}];RACSignal *desc = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {if ([value[@"desc"] isKindOfClass:[NSString class]]) {return [RACSignal return:value[@"desc"]];} else {return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];}}];RACSignal *renderedDesc = [desc flattenMap:^RACStream *(NSString *value) {NSError *error = nil;RenderManager *renderManager = [[RenderManager alloc] init];NSAttributedString *rendered = [renderManager renderText:value error:&error];if (error) {return [RACSignal error:error];} else {return [RACSignal return:rendered];}}];RAC(self.someLablel, text) = [[title catchTo:[RACSignal return:@"Error"]]  startWith:@"Loading..."];RAC(self.originTextView, text) = [[desc catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];RAC(self.renderedTextView, attributedText) = [[renderedDesc catchTo:[RACSignal return:[[NSAttributedString alloc] initWithString:@"Error"]]] startWith:[[NSAttributedString alloc] initWithString:@"Loading..."]];[[RACSignal merge:@[title, desc, renderedDesc]] subscribeError:^(NSError *error) {UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error" message:error.domain delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];[alertView show];}];

当然,细心的同学会发现这样修改,仍然有许多计算上的浪费,例如将fetchData转换为title的block会执行多次,将fetchData转换为desc的block也会执行多次。但是由于这些block都是无副作用的,计算量并不大,可以忽略不计。如果计算量大的,也需要对中间的信号进行热信号的转换。不过请不要忽略冷热信号的转换本身也是有计算代价的。

好的,写到这里,我们终于揭开RAC中冷信号与热信号的全部面纱,也知道如何使用了。希望这个系列文章可以让大家更好地了解RAC,避免使用RAC遇到的误区。谢谢大家。

美团iOS组有很多志同道合的小伙伴,对于各种技术都有着深入的了解,我们热忱地欢迎一切牛掰的小伙伴加入,共同学习,共同进步。(简历请发送到邮箱 liangsi02@meituan.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/478003.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode 1260. 二维网格迁移(二维转一维)

1. 题目 给你一个 n 行 m 列的二维网格 grid 和一个整数 k。你需要将 grid 迁移 k 次。 每次「迁移」操作将会引发下述活动&#xff1a; 位于 grid[i][j] 的元素将会移动到 grid[i][j 1]。 位于 grid[i][m - 1] 的元素将会移动到 grid[i 1][0]。 位于 grid[n - 1][m - 1] …

困惑度、主题一致性,lda模型找出主题相关词

困惑度perplexity&#xff1a;句子的概率的倒数。如果句子的概率越大&#xff0c;说明这句话越符合人话的规律&#xff0c;即p&#xff08;句子&#xff09;&#xff0c;pp困惑度越小。模型对该句子就越不困惑。 通俗一点解释下就是&#xff0c;困惑度表示的对于一篇文章来说…

patch成为了ALL You Need?挑战ViT、MLP-Mixer的简单模型来了

文 | 机器之心编辑部源 | 机器之心ViT&#xff08;Vision Transformer&#xff09;等视觉模型的强大性能&#xff0c;是来自于 Transformer&#xff0c;还是被忽略的 patch&#xff1f;有研究者提出了简单 ConvMixer 模型进行证明&#xff0c;直接将 patch 作为输入&#xff0c…

使用 npm shrinkwrap 来管理项目依赖

管理依赖是一个复杂软件开发过程中必定会遇到的问题。 在Node.js项目开发的时候&#xff0c;我们也经常需要安装和升级对应的依赖。虽然 npm 以及语意化的版本号 (semantic versioning, semver) 让开发过程中依赖的获取和升级变得非常容易&#xff0c; 但不严格的版本号限制&am…

OpenKG开源系列 | 轻量级知识图谱抽取开源工具OpenUE

OpenKG地址&#xff1a;http://openkg.cn/tool/openueGitHub地址&#xff1a;https://github.com/openkg-org/openueGitee地址&#xff1a;https://gitee.com/openkg/openueOpenUE网站&#xff1a;http://openue.openkg.cn论文地址&#xff1a;https://aclanthology.org/2020.e…

通俗理解kaggle比赛大杀器xgboost

原文链接 通俗理解kaggle比赛大杀器xgboost 说明&#xff1a;若出现部分图片无法正常显示而影响阅读&#xff0c;请以此处的文章为准&#xff1a;xgboost 题库版。 时间&#xff1a;二零一九年三月二十五日。 0 前言 xgboost一直在竞赛江湖里被传为神器&#xff0c;比如时不时某…

LeetCode 690. 员工的重要性(图的DFSBFS)

文章目录1. 题目2. 解题2.1 DFS2.2 BFS1. 题目 给定一个保存员工信息的数据结构&#xff0c;它包含了员工唯一的id&#xff0c;重要度 和 直系下属的id。 比如&#xff0c;员工1是员工2的领导&#xff0c;员工2是员工3的领导。他们相应的重要度为15, 10, 5。那么员工1的数据结…

论文浅尝 | LGESQL: 结合线性有向图以及普通有向图的自然语言转换SQL语句模型...

LGESQL: Line Graph Enhanced Text-to-SQL Model with Mixed Local and Non-Local Relations笔记整理&#xff1a;郭心南来源&#xff1a;ACL2021链接&#xff1a;https://arxiv.org/abs/2106.01093Github地址&#xff1a;https://github.com/rhythmcao/text2sql-lgesql概述本文…

召回粗排精排-级联漏斗(上)

文 | 水哥源 | 知乎saying1. 把点击多的item曝光提升是推荐模型的基本能力&#xff0c;也是基本要求。后验不好的曝光提高也可能是好模型&#xff0c;后验好的曝光不提高&#xff0c;可以开除推荐模型了2. 在起量阶段&#xff0c;各路item需要争宠&#xff0c;谁能最快博取精排…

美团Android资源混淆保护实践

前言 Android应用中的APK安全性一直遭人诟病&#xff0c;市面上充斥着各种被破解或者汉化的应用&#xff0c;破解者可以非常简单的通过破解工具就能对一个APK进行反编译、破解、汉化等等&#xff0c;这样就可以修改原有代码的逻辑、添加新代码、添加或修改资源、或者更有甚者植…

细说ReactiveCocoa的冷信号与热信号(二):为什么要区分冷热信号

前一篇文章我们介绍了冷信号与热信号的概念&#xff0c;可能有同学会问了&#xff0c;为什么RAC要搞得如此复杂呢&#xff0c;只用一种信号不就行了么&#xff1f;要解释这个问题&#xff0c;需要绕一些圈子。 前面可能比较难懂&#xff0c;如果不能很好理解&#xff0c;请仔细…

jupyter 扩展工具添加自动补全代码功能+pep8+字体大小+代码行号+拼写检查+目录索引+代码自动补全

自动补全代码功能pep8字体大小代码行号拼写检查目录索引代码自动补全 我知道你会用Jupyter Notebook&#xff0c;但这些插件你都会了吗&#xff1f;

搞不懂,为啥现在什么公司都在考算法???

昨天在知乎上刷到一个热门问题:程序员需要达到什么水平才能顺利拿到 20k 无压力&#xff1f;其中一个最热门的回答是&#xff1a;“其实&#xff0c;无论你是前端还是后端、想进大厂还是拿高薪&#xff0c;算法都一定很重要。”为什么&#xff0c;算法会如此重要&#xff1f;不…

OpenKG开源系列 | 中文高中地理知识图谱CKGG(南京大学)

OpenKG地址&#xff1a;http://openkg.cn/dataset/ckggGitHub地址&#xff1a;https://github.com/nju-websoft/CKGG数据地址&#xff1a;https://doi.org/10.5281/zenodo.4668711论文地址&#xff1a;https://doi.org/10.1007/978-3-030-88361-4_25开放许可协议&#xff1a;CC…

LeetCode 892. 三维形体的表面积(数学)

1. 题目 在 N * N 的网格上&#xff0c;我们放置一些 1 * 1 * 1 的立方体。 每个值 v grid[i][j] 表示 v 个正方体叠放在对应单元格 (i, j) 上。 请你返回最终形体的表面积。 示例 1&#xff1a; 输入&#xff1a;[[2]] 输出&#xff1a;10示例 2&#xff1a; 输入&#x…

细说ReactiveCocoa的冷信号与热信号(一)

背景 ReactiveCocoa&#xff08;简称RAC&#xff09;是最初由GitHub团队开发的一套基于Cocoa的FRP框架。FRP即Functional Reactive Programming&#xff08;函数式响应式编程&#xff09;&#xff0c;其优点是用随时间改变的函数表示用户输入&#xff0c;这样就不需要可变状态了…

淘宝数据,数据处理,时间序列分析,获客分析,购买路径分析

-- 创建数据库 create database taobao; use taobao; desc use_behaviour;-- 创建表格 create table use_behaviour( user_id int(9), item_id int(9), category_id int(9), behaviour_type varchar(5), timestamps int(14));-- 查询已导入多少条 select count(*) from use_…

谁说发 paper 一定要追快打新?2021年,研究 word2vec 也能中顶会!

文 | jxyxiangyu前言“小夕&#xff0c;小夕&#xff0c;你关注的任务sota又被刷新了&#xff01;”“什么&#xff1f;&#xff01;”还在跑实验的小夕默默流下了辛酸泪不得不说nlp领域的发展真的太快了&#xff0c;炼丹师们不光要时刻关注前沿热点&#xff0c;还要快速做出实…

论文浅尝 | Multilingual LAMA: 探索多语言预训练语言模型中的知识

笔记整理&#xff1a;谭亦鸣&#xff0c;东南大学博士生来源&#xff1a;EACL‘21链接&#xff1a;https://aclanthology.org/2021.eacl-main.284.pdf概述本文关注将语言模型&#xff08;LM&#xff09;视作一个知识库&#xff0c;然后用于解决例如句子填空这样的NLP任务&#…

LeetCode 860. 柠檬水找零(贪心)

1. 题目 在柠檬水摊上&#xff0c;每一杯柠檬水的售价为 5 美元。 顾客排队购买你的产品&#xff0c;&#xff08;按账单 bills 支付的顺序&#xff09;一次购买一杯。 每位顾客只买一杯柠檬水&#xff0c;然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零&…