2024年7月

要在Objective-C中创建一个高度可复用的工具类,以防止按钮的暴力点击,并且使用切面编程(AOP)的方式,我们可以考虑使用
Aspects
这个库来实现方法的拦截。以下是具体的实现步骤:

第一步:引入Aspects库

首先,需要将
Aspects
集成到项目中。
Aspects
是一个轻量级的AOP框架,允许你在运行时拦截类的实例方法和类方法。你可以通过CocoaPods来添加此库:

pod 'Aspects'

第二步:创建UIButton的Category

接下来,创建一个UIButton的Category,用于添加防暴力点击的逻辑。

UIButton+PreventSpam.h
:

#import <UIKit/UIKit.h>

@interface UIButton (PreventSpam)

// 设置间隔时间,进行防暴力点击设置
- (void)setPreventSpamInterval:(NSTimeInterval)interval;

@end

UIButton+PreventSpam.m
:

#import "UIButton+PreventSpam.h"
#import <objc/runtime.h>
#import <Aspects/Aspects.h>

static const char *UIButton_preventSpamIntervalKey = "UIButton_preventSpamIntervalKey";

@implementation UIButton (PreventSpam)

- (void)setPreventSpamInterval:(NSTimeInterval)interval {
    objc_setAssociatedObject(self, UIButton_preventSpamIntervalKey, @(interval), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    // 使用Aspects进行方法拦截
    [self aspect_hookSelector:@selector(sendAction:to:forEvent:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> aspectInfo, SEL action, id target, UIEvent *event) {
        UIButton *button = aspectInfo.instance;
        if (button.isIgnoring) {
            return;
        }
        button.isIgnoring = YES;

        // 执行原始的 sendAction:to:forEvent: 方法
        // 这里直接调用原始方法是不合适的,因为这是一个拦截后的block,我们要重新invoke original invocation
        [[aspectInfo originalInvocation] invoke];
        
        // 延迟间隔时间后,重置为可以点击
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            button.isIgnoring = NO;
        });
    } error:NULL];
}

// 判断当前是否应该忽略点击,通过关联对象添加属性
- (void)setIsIgnoring:(BOOL)isIgnoring {
    objc_setAssociatedObject(self, @selector(isIgnoring), @(isIgnoring), OBJC_ASSOCIATION_ASSIGN);
}

- (BOOL)isIgnoring {
    return [objc_getAssociatedObject(self, @selector(isIgnoring)) boolValue];
}

@end

使用

在适当的位置设置按钮的点击间隔:

#import "UIButton+PreventSpam.h"

[myButton setPreventSpamInterval:1.0]; // 点击间隔设置为1秒

注意

  1. 本示例使用了
    Aspects
    库进行方法的拦截,这是一种运行时AOP的技巧。这里利用了Objective-C的动态特性。AOP可以帮助增加代码的可维护性和可复用性,但它也会增加代码的复杂性,使用时需要权衡。

  2. 使用关联对象(
    objc_setAssociatedObject

    objc_getAssociatedObject
    )来给UIButton动态添加属性,用于存储是否应该忽略点击和间隔时间,这是Objective-C扩展现有类功能的常用技巧。

  3. 在实际项目中,可能需要考虑多种情况和细节,如处理快速切换界面时按钮点击事件的响应等。

一、摘要

众所周知,Java 经过多年的发展,已经从一门单纯的计算机编程语言,发展成了一套成熟的软件解决方案。从互联网到企业平台,Java 是目前使用最广泛的编程语言。

以下这段内容是来自 Java 的官方介绍!

从笔记本电脑到数据中心,从游戏控制台到科学超级计算机,从手机到互联网,Java 无处不在!

无论是是互联网公司还是传统的企业,在研发数字化平台系统时,世界上绝大部分的公司基本上都会首选采用 Java 来开发,以助力企业的数字化转型和生产效率的提升,原因就不多说了,
安全、稳定、高性能、跨平台
等等,其实还有一个很大的优势:好招人,哈哈哈~~

而 Java 之所以如此有魅力,不仅限于它优秀的程序语言设计,其中还有一个很重要的原因得益于它的虚拟机平台设计,通过它的虚拟机可以轻松实现应用程序跨平台运行,实现“一次编写,到处运行”的目标,并且还可以取得不错的运行效率。

二、Java 简介

在介绍虚拟机之前,我们还是简单的回顾一下 Java 的发展故事。

Java 最早是由 SUN 公司的一个小工程师团队在上个世纪 90 年代初开发的一种编程语言,其中
詹姆斯·高斯林
是项目负责人(人称 Java 之父),编程语言最初被命名为 Oak,以他们的办公室外的橡树而命名,后来因为 Oak 已经被人注册了,因此重新改名成 Java。

在此,我们附上一张 Java 之父的光辉图片!

当时的目标是将其用于开发数字有线电视行业的嵌入式应用程序,例如机顶盒、智能电视等数字设备,实现无障碍的运行。结果推出以后,市场没啥反响。4 年后,技术小组不得不解散。

但也就在此时,互联网刚刚崛起,恰巧这时 MarkArdreesen 开发的 Mosaic 和 Netscape 启发了 Oak 项目组成员,他们利用 Java 编写了 HotJava 浏览器,HotJava 拥有了在页面中运行 Java 代码的能力,可以无障碍的运行在各个电脑设备上,这个效果得到了 Sun 公司首席执行官 ScottMcNealy 的支持,触发了 Java 进军 Internet 的决心。

这在当时引起了不小的轰动,人们从中似乎看到了互联网未来该有的样子,一时间,Java 受到了众多厂商和开发者的追捧。于是趁热打铁,Sun 在 1996 年正式发布了 JDK1.0,当时最主要的十个操作系统厂商宣布,将在它们的系统中支持 Java 程序的运行,从此在互联网高速发展中大杀四方。

Java 之所以能在互联网发展中获得广泛的开发者支持,一个很重要的原因就是它可以实现“
一次编写,到处运行
”的效果,
极大的省去了软件开发工作量
,能实现这种效果主要得益于 Java 的架构理念设计。

像 C、C++ 等编程语言,代码是直接编译成机器码执行,虽然很快,但是不同的平台(x86、ARM等) CPU 的指令集也不同,每次开发一个应用程序需要编译出每一种平台的对应机器码,非常麻烦。

Java 则不同,它会将代码编译成一种“字节码”,它类似于抽象的 CPU 指令,然后虚拟机负责加载字节码,根据不同的平台编译成对应的机器码并执行,这样就可以实现了“
一次编写,到处运行
”的效果。

当然,对于虚拟机,需要针对每个平台分别开发,为了保证不同平台、不同公司开发的虚拟机都能正确执行 Java 字节码,SUN 公司制定了一系列的 Java 虚拟机规范。从实践的角度来看,虚拟机的兼容性做得非常好,低版本的 Java 字节码完全可以正常运行在高版本的虚拟机上。

整个 JDK 内部结构,可以用如下图来概括(图片来自于
廖雪峰的Java介绍

JDK、JRE 和 JVM 三者之间的关系,可以用如下内容来简要描述。

  • JDK(Java Development Kit):指的是 Java 语言的软件开发工具包,包括编译器、调试器、Java API 类库、Java 虚拟机等内容,通常用于开发环境
  • JRE(Java Runtime Environment):指的是 Java 运行时环境,包括 Java API 类库、Java 虚拟机等内容,通常用于运行 Java 应用程序,通常用于生产环境
  • JVM(Java Virtual Machine):指的是 Java 虚拟机,用于在不同的平台上执行 Java 应用程序

可以清晰的看到,JVM 是 Java 应用程序能实现跨平台运行的核心。因此,了解和学习 JVM 相关的知识,有助于开发者对 Java 相关技术有着更全面的认识。

在操作系统的命令行,输入
java -version
,即可看到当前 JDK 采用的是哪种版本的虚拟机。

三、虚拟机简介

说起 Java 虚拟机,许多开发者可能首先会想到 HotSpot 虚拟机,正如上文的截图部分。

实际上 Java 虚拟机除了 HotSpot 之外,还有 Sun Classic VM、Exact VM、BEA JRockit VM、IBM J9 VM等等,虚拟机的发展过程,可以用
百花齐放
这四个字来概括。

3.1、Sun Classic VM

在 1996 年,Sun 发布 JDK 1.0 时,其中自带的虚拟机就是 Classic VM。这款虚拟机使用的是纯解释器的方式来执行 Java 代码,简单的说就是边解释边运行代码,并非像现在这样通过编译器的方式来执行代码(先编译、后执行)。在 Sun Classic 虚拟机中,解释器与编译器无法共同存在,因此 Java 应用程序执行速度上肯定快不起来,只能说够用。

在 1998 年,Sun 在发布 JDK 1.2 时,同时也公布了一款名为 Exact VM 的虚拟机,这款虚拟机尝试解决了 Classic VM 遇到的所有问题。它的执行系统解决了 Classic VM 存在的解释器和编译器无法同时工作的问题,同时还具备了一些现代高性能处理器的特性,如:两级即时编译、准确式内存管理等。

但可惜的是,这款虚拟机一直没有真正被大规模使用过,此时 Sun Classic VM 依然作为默认的 Java 虚拟机。

3.2、HotSpot VM

在 2000 年,Sun 在发布 JDK 1.3 时,公布将 HotSpot VM 作为默认的 Java 虚拟机,也就是大家广为流传的 Java 虚拟机。

实际上,这个虚拟机并不是由 Sun 公司原生开发的,而是由一个叫 Longview Technologies 公司开发的,Sun 公司注意到了这款虚拟机在 JIT 编译上的许多优秀成果,于 1997 年收购了 Longview Technologies 公司,从而获得了 HotSpot VM 的所有权。

HotSpot VM 不仅仅有前面说到两款虚拟机的优点,也有许多自己的新技术,例如:热点探测技术。热点探测技术指的是通过执行计数器找出最具优化价值的代码,然后通知 JIT 编译器以方法为单位进行深度优化编译。

其实 Exact VM 中也有类似的技术,但最终还是选择了 HotSpot 作为默认的虚拟机。

HotSpot VM 作为默认的 Java 虚拟机之后,直到现在,依然是我们最常用的虚拟机,可见 HotSpot VM 的生命力之顽强!

3.3、BEA JRockit / IBM J9 VM

前面说的是 Sun 公司研发的虚拟机,但除了 Sun 公司之外,其他组织、公司也研发过不少的虚拟机实现,其中最著名的就是 BEA 公司的 JRockit VM 和 IBM 公司的 J9 VM 了。

JRockit VM 是除 HotSpot 之外另一款响当当的虚拟机,BEA 公司在 2002 年收购的了此虚拟机的开发商。这款虚拟机在相当一段时间里获称
世界上速度最快的 Java 虚拟机
,它是一款专注于服务器端应用的虚拟机,并且它的垃圾收集算法相比其它所有虚拟机表现更佳;此外,其提供的 MissionControl 服务套件也十分强大。

IBM 公司的 J9 VM 则是一款比较通用的虚拟机,其定位应用于从服务端到桌面应用,再到嵌入式的多用途虚拟机。IBM 公司开发 J9 VM 的目的是将其作为 IBM 公司各种 Java 产品的执行平台。稍有不同的是,IBM J9 VM 都只能跟 IBM 产品一起使用,虽然不存在技术限制,但是需要单独的商业许可证。

J9 VM 的性能水平大致跟 HotSpot VM 是一个档次的,在一些场景下,有时 HotSpot VM 快些,有时 J9 VM 快些。

3.4、其它 JVM

HotSpot、JRockit 和 J9 这三个虚拟机,曾经在很长的时间并称三大主流 JVM,可以说是 Java 虚拟机发展史上比较耀眼的部分。除此之外,其实还有各种各样的小范围使用的虚拟机。

例如 Azul VM 和 BEA Liquid VM 的专用商业虚拟机,性能非常强悍,可以管理至少数十个 CPU 和数百 GB 的内存资源,还提供在巨大内存范围内实现可控 GC 时间的垃圾收集器等等,这些虚拟机只运行在特定硬件平台,因此要求也比较高。

此外还有许许多多其他的虚拟机存在,例如:Apache Harmony、Google Android Dalvik VM、Mircosoft JVM 等等,都在各自领域发挥作用。

3.5、Oralce

谁都没想到 Sun 公司技术如此的强大,却在之后的岁月里逐渐走向陨落,因为不懂销售和运营,导致公司财务逐渐出现亏损,在 2009 年,Oracle 公司以现金方式收购 Sun 公司,交易价格达 74 亿美元。

此前,Oracle 公司在 2008 年收购了 BEA 公司,获得了 JRocket VM 的所有权;这意味着 JRockit 与 HotSpot 同属于 Oracle 所有。

实际上,现在 JDK1.8 的 HotSpot VM 已经是以前的 HotSpot VM 与 JRockit VM 的合并版,也就是传说中的“HotRockit”,只是产品里名字还是叫 HotSpot VM。

后续的 JVM 发展,相信会越来越好,我们拭目以待!

四、小结

本文主要围绕 Java 的发展历史,对虚拟机的诞生历史做了一次知识总结,在后续的文章中,我们会对 JVM 相关知识进行总结和分享,如果有描述不对的地方,欢迎大家留言指出,不胜感激!

五、参考

1.
https://www.liaoxuefeng.com/wiki/1252599548343744/1255876875896416

2.
https://www.cnblogs.com/xrq730/p/4826691.html

3.
https://www.cnblogs.com/chanshuyi/p/jvm_serial_02_the_history_of_jvm.html

2024年7月16日,大暑将至,立秋不远。我们基于Python的转录组学全分析框架的文章——"OmicVerse: a framework for bridging and deepening insights across bulk and single-cell sequencing"——正式在
Nature Communication
上发表了,这是我们课题组第一个里程碑意义的成果,也是我第一篇作为第一作者发表在Nature系列期刊上的文章,对我们的成果感兴趣的朋友可以查看文末的【原文链接】。

我在去年7月份的时候,将OmicVerse的第一个版本发布到了预印本网站Biorxiv上,并在“生信技能树”等公众号上撰写了一些大致的介绍。在这里,我想写一些OmicVerse开发背后的故事,以及该框架的愿景是什么。

OmicVerse组件概览

框架起源

我应该是大二上的时候开始接触生物信息学,那会儿单细胞测序刚刚兴起,生信还是一片蓝海,那会儿你会一个RNA-seq的分析,都能被课题组当成是一个宝贝。我算是半个科班出身,得益于计算机的双学位,我在接触生物信息学的时候,没有遇到太大的门槛,调包-分析,一气呵成。

但是很快我遇到了第一个问题,那就是对R语言的不熟悉,以至于很多依赖出错的时候,我没法去手动修改,并且R语言的语法对C++出身的程序员来说,并不是什么友好的代码规范,我也尝试过tidyverse,但最后还是放弃了。在阅读了转录组分析的统计学模型后,我开始萌生了一个想法:“我为什么不自己用Python复现转录组分析的算法呢?“

Paul Pavlidis, UBC

说时迟那时快,我很快便把ttest,foldchange等统计学模型应用到了RNA-seq上,然后使用matplotlib(Python的绘图包)完成了可视化,然后为了方便,我开始自学Python包制作,我将这些函数,全部封装进了一个包,叫Pyomic。这时候,我也大三了。同时在转录组分析的时候,写了第一个算法
ERgene
,这是一个快速计算内参基因的算法,这个算法最开始是用来解决蛋白质组中的内参定量的问题,常见的内参有时候在质谱中找不到。得益于我的老师杜宏武的鼓励,我将这个包封装测试后,发表在了
Scientific Report
上,时至今日,我还是很喜欢这个算法,因为我在做benchmark的时候,在电脑装了windows7的虚拟机运行excel2003来跑一些古老的算法。

我的成绩还可以,拿到了我们学校的保研资格,于是到处去面试,跟老师们交流,印象比较深的有两件事:

  • 第一件是在北京大学面试的时候发生的,当时可能觉得MIT协议很好,于是Pyomic的协议也是MIT。在面试的时候,高歌老师直接指出了这个问题,问了我两遍,Pyomic的算法都是自己写的吗?我当时引入了一个外部dynamicTree的模块,这是WGCNA的核心算法,但我觉得我整个分析都是自己实现的,于是就被diss了,问我为啥把别人GPL的协议给继承成了MIT。自此,我对著作权,有了一个更清晰的认识。
  • 第二件是在中山大学面试的时候发生的,当时有个PI问我,你觉得Pyomic有什么意义吗?就是复现别人的算法?时至今日,这个问题有了一个很好的答案,我们为Python分析转录组学奠定了一个分析标准,统一了各类接口,这是一个很值得做的事情。

OmicVerse的正式提出

OmicVerse这个名字,是在2023年4月份的时候,也就是我博一的时候确定的。当时我在处理单细胞转录组的数据,遇到了两个让我很烦躁的问题:”包安装的依赖冲突“和”不同包的输入输出差异极大“,这两件事严重拖低了我的分析效率,我便在想,我能否统一这些算法,使其安装不报错,输入只需要一种格式就好了。于是,我便开始研究算法们背后的输入和输出。然后写着写着,就封装了大量的算法,因为顺手,所以这些算法都是扔进了Pyomic这个包中进行实现。

受到scvi-tools的启发,这是一个用于单细胞测序的深度学习模型框架,我就想弄一个更全面的基于Python的单细胞测序分析框架。Pyomic感觉这个名字,并不能准确反映我想做的事,在几经查阅英文命名后,有一天晚上散步的时候,看着天上的星星,突然想到,为啥我不用“Verse”结尾呢?于是便有了OmicVerse,一个月的时间,便把所有算法封装好,并且撰写按Brief Communication的形式撰写好了手稿。

OmicVerse的logo

但还没开始投,投稿前总是需要问一问的。于是,我就分别给“Nature Biotechnology”和”Nature Method“的编辑写信,问了一下他们对这篇稿件的兴趣,叫做投稿前预询问,然后Method的编辑很高兴,让我们投过去,但是Biotechnology的编辑就差没把”Rubbish“两字写邮件里了。我确实是不死心的,死也要死的明白点,想问一下我到底缺了什么,后面在我的再三恳切地询问后,她告诉我说,没有biological insight。有时候,灵感就是一瞬间的事儿,我既然分别封装了Bulk RNA-seq和single cell RNA-seq,这缺一个桥梁算法啊!不过一查,Bulk2single有人做过了,single2bulk就更不用说。不过有趣的是,我在此前开发了一个轨迹推断的算法,这个算法在一些数据集上效果怎么都差,我就在想,我可以使用Bulk来插补轨迹呀!于是,这就是BulkTrajBlend的雏形,后面做了一系列工作来完善,这是后话了。

于是我就兴高采烈地,投到了Nature Method的投稿系统,然后。。。一个月的等待,给我拒了,让我转NC,我也不能说什么,NC就NC吧,毕竟我博士期间还没论文呢。后来今年3月份的时候,我看见了Method上发了tidyomics,我始终没想明白,OmicVerse比这个算法差在哪里了?

不同方法的对比

OmicVerse的壮大

此前似乎是询问问题,我加了Python生物人的公众号的作者,在OmicVerse投出去之前我先挂了预印本,然后想试试推广,看看广大用户对于OmicVerse的反馈如何,于是便拜托Python生物人写了一篇推送,并且带上”清华大学“的tag,但似乎受众寥寥。

我觉得,不应该啊,我这个框架这么好用!于是,就拜托健明老师,在”技能树“上发文。但令我意外的是,健明老师特别喜欢这个框架,给了我大大的鼓励,并且着重宣传了一下Python,国产,全面等字眼,然后还成立了社群,自此,OmicVerse便开始被人所知晓,但是没文章,大家可能更多持一个观望的态度。以至于,到2024年4月,才有了第一个引用。

此外,我觉得,一个好用的算法,而且还是框架,不配置全面的教程怎么可以。所以我的用户文档,写的非常非常详细,这也是受一些算法写的不详细的迫害所养成的习惯,你写的越详细,就越方便他人,大家也就更愿意去使用。

我觉得我做的最明智的一件事,那就是撰写了
单细胞分析最好的教程——中文版
,该教程脱胎于febain组的单细胞最佳实践,但是,我在原教程的基础上,加上了大量我自己的理解以及新算法,同时新算法使用OmicVerse作为统一接口,还开发了大量漂亮的可视化函数,用户也因此,慢慢变多了。我觉得互联网开源的精神食粮,是用户们的正反馈,用爱发电,大家开心,我也就有动力去迭代下一个版本,目前经过一年,已经1.6.4了。

后记

从我投稿,到正式接收,满打满算正好一年整,实际上如果不是一个审稿人失联了,可能在寒假,就接收了。当然这也是无法抗拒的外因,只能说,在暑假发表,阴差阳错吧。后续还会有OmicVerse2,OmicFate等一系列算法和框架,大家敬请期待~

特别感谢我在中山大学夏令营面试的时候认识的熊远妍老师,也非常欢迎大家报考她的课题组,她是一个非常非常open的人,课题组的氛围可以说是相当ok,她算是我在生信领域第一个真正意义上的指导老师。此外还特别谢谢健明老师,大家可能也挺熟悉的,如果不是他提供了这样一个平台,还替我大力宣传,我觉得OmicVerse的用户可能依然寥寥,而且很有意思的是,我跟健明老师算是不打不相识,原因忘了是什么了,好像是我批判老师一些事情来着,狠狠地怼了。

最后的最后,大家可能会好奇我的导师,是一个怎样的人,能让我几乎完全自由和独立地去探索自己想做的课题,没有任何限制。并在合适的时候,提供一些相应的指导,我的导师杜宏武教授,就是这么一个有趣的人,在课题组,你的兴趣得到充分地尊重,只要你想做,你有动力,那么这件事就是可行的。

暑假到了,祝大家假期愉快~

作者介绍

曾泽华
,00年生,北京科技大学生物技术、物联网工程本科双学位,北京科技大学化学博士在读,主要研究方向是单细胞多组学算法的开发及癌症和发育生物学中的基因动态调控。博士期间作为第一作者开发了转录组学生态框架
OmicVerse
,细胞命运因子推断算法
OmicFate
,单细胞轨迹恢复算法
BulkTrajBlend
。目前以第一或通讯(含共同)作者身份在
Nature Communication

Advanced Function Material
等杂志发表多篇研究论文,论文总影响因子100+。

[NOIP2008 提高组] 笨小猴

题目描述

笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼。但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大!

这种方法的具体描述如下:假设maxn是单词中出现次数最多的字母的出现次数,minn是单词中出现次数最少的字母的出现次数,如果maxn-minn是一个质数,那么笨小猴就认为这是个 Lucky Word,这样的单词很可能就是正确的答案。

输入格式

一个单词,其中只可能出现小写字母,并且长度小于100。

输出格式

共两行,第一行是一个字符串,假设输入的的单词是 Lucky Word,那么输出
Lucky Word
,否则输出
No Answer

第二行是一个整数,如果输入单词是
Lucky Word
,输出
maxn-minn
的值,否则输出 0。

样例 #1

样例输入 #1

error

样例输出 #1

Lucky Word
2

样例 #2

样例输入 #2

olympic

样例输出 #2

No Answer
0

提示

【输入输出样例 1 解释】

单词
error
中出现最多的字母 r 出现了 3 次,出现次数最少的字母出现了 1 次,3-1=2,2是质数。

【输入输出样例 2 解释】

单词
olympic
中出现最多的字母 出现了 1 次,出现次数最少的字母出现了 1 次,1-1=0,0 不是质数。

(本处原题面错误已经修正)

noip2008 提高第一题

一道有关于字符串的题目--

这道题我们大致可以分为以下几个步骤:
1.输入一个字符串,并遍历这个字符串,设立Numbers数组(我们暂且可以将Numbes数组初始化为0),存储这个字符串中出现字符的个数.

2.遍历这个数组,并找出这个数组中最小的,并且字符串中出现过的字符的次数的最小值minn和最大值maxn(最小值的判断有一个坑——那就是我们不能仅仅找出数组中的最小值,同时这个最小值必须得>0,
表示这个字符在输入的字符串中出现过)

3.定义一个函数,判断一个整数是否是质数

4.找出最小值和最大值以后,定义diff变量=maxn-minn,判断这个数是否是质数,如果不是质数,第一行输出"No Answer",第二行输出0即可。
如果这个数是质数,则第一行输出"Lucky Word",第二行输出diff这个变量的值就可以了。

具体可以编译运行的代码如下:

#include <iostream>
#include <cmath>
using namespace std;
// 判断一个数是否是质数
bool isPrime(int n) {
    if (n <= 1) {
        return false;
    }
    for (int i = 2; i <= sqrt(n); i++) {
        if (n % i == 0) {
            return false;
        }
    }

    return true;
}
int main() {
    //输入字符串
    string s;
    cin >> s;
    //利用Numbers数组,来判断这个字符串中出现的字母的次数
    int Numbers[26] = { 0 };
    for (int i = 0; i < s.size(); i++) {
        int n = (s[i] - 'a');
        Numbers[n]++;
    }

    int maxn = 0, minn = 110;
    for (int i = 0; i < 26; i++) {
        if (Numbers[i] > maxn) maxn = Numbers[i];
        //在这里一定要注意,不能直接比对Numbers数组中最小的那位,而是Numbers数组中最小的那位,并且它的值不能等于0(即这个字母至少在字符串中出现过一次)
        if (Numbers[i] > 0 && Numbers[i] < minn) minn = Numbers[i];
    }

    int diff = maxn - minn;
    if (isPrime(diff)) {
        cout << "Lucky Word" << endl;
        cout << diff;
    }
    else {
        cout << "No Answer" << endl;
        cout << 0;
    }
    return 0;
}


这道题我想告诉大家的是,并不是在遍历数组找最小值的时候,一定只有最小值这个逻辑条件。
比如这道题,不仅要找最小值,并且这个最小值还不能是0,一定要清楚的判断我们的需求,然后根据这个需求来书写判断逻辑

前言

上一篇文章说了重启 sentry 的事

因为过程太折腾了,一度想过放弃 sentry 换成其他比较轻量级的开源监控系统

这不就给我找到了另外俩个

这次就来试试这个 glitchtip

用了之后才发现,这个也是用 Django 开发的,而且兼容 sentry 协议,连 SDK 都用的 sentry 的…

就是个 sentry 的轻量级平替版

环境准备

相比起 sentry ,glitchtip 非常的轻量,只用到了 redis 和 celery

日志数据是存在 PostgreSQL 里的

这就很舒服了,部署起来比 sentry 这种重量级的框架简单多了

本文的部署基于官方的 docker-compose 配置修改而来

PostgreSQL

首先准备数据库

官方的 compose 里带有一个数据库,不过我服务器上已经有数据库了,就不要重复搞那么多容器

services:
  db:
    image: postgres
    container_name: pgsql
    restart: unless-stopped
    environment:
      - POSTGRES_PASSWORD=数据库管理员密码
    expose:
      - 5432
    ports:
      - 5432:5432
    volumes:
      - ./data:/var/lib/postgresql/data
    networks:
      - default

networks:
  default:
    name: pgsql

把pgsql启动之后,先来创建 glitchtip 用到的数据库和用户

-- 创建用户: 使用 CREATE ROLE 或 CREATE USER 命令来创建一个新用户。
CREATE USER glitchtip WITH PASSWORD 'glitchtip用户密码';

-- 赋予权限: 要确保 glitchtip 用户只能访问 glitchtip 数据库,你需要为该用户设置适当的权限。
GRANT ALL PRIVILEGES ON DATABASE glitchtip TO glitchtip;

-- 授予在 public 模式中创建表的权限
GRANT CREATE ON SCHEMA public TO glitchtip;
-- 授予在 public 模式中使用的权限
GRANT USAGE ON SCHEMA public TO glitchtip;

这里数据库的就搞定了

扩展:adminer

如果想在网页上管理数据库,可以启动一个 adminer 服务

services:
  adminer:
    image: adminer
    container_name: adminer
    restart: always
    networks:
      - swag
      - pgsql
      - mysql

networks:
  swag:
    name: swag
    external: true
  pgsql:
    name: pgsql
    external: true
  mysql:
    name: mysql
    external: true

启动之后在 swag 做一下8000端口的反代即可。

Redis

redis 比较轻,建议使用 glitchtip 里的即可,不需要使用共用的 Redis

除非搭了集群

安装 glitchtip

我修改了一下官方提供的 compose 配置

有几点需要注意:

  • 邮箱地址和密码需要使用
    urllib.parse.quote()
    转义,我这里依然使用企业邮箱,不过试了
    smtp://

    smtps://
    前缀都没法发送成功
  • 把官方的顶层volumes去掉了,把数据放在当前目录下,方便管理。如果有大量数据可以考虑换 OSS
  • 使用我们前面部署的 PostgreSQL 数据库,而不是 compose 里另外起一个
x-environment: &default-environment
  DATABASE_URL: postgres://glitchtip:glitchtip用户密码@pgsql:5432/glitchtip
  SECRET_KEY: 建议32位随机密码 # 可以使用命令生成 openssl rand -hex 32
  PORT: 8000
  EMAIL_URL: smtp://邮箱地址:邮箱密码@smtp.exmail.qq.com:465
  GLITCHTIP_DOMAIN: https://glitchtip.example.com
  DEFAULT_FROM_EMAIL: 邮箱地址
  CELERY_WORKER_AUTOSCALE: "1,3"
  CELERY_WORKER_MAX_TASKS_PER_CHILD: "10000"

x-depends_on: &default-depends_on
  - redis

services:
  redis:
    image: redis
    restart: unless-stopped
    networks:
      - default
  
  web:
    image: glitchtip/glitchtip
    depends_on: *default-depends_on
    ports:
      - "8000:8000"
    environment: *default-environment
    restart: unless-stopped
    volumes:
      - ./uploads:/code/uploads
    networks:
      - default
      - pgsql
      - swag
  
  worker:
    image: glitchtip/glitchtip
    command: ./bin/run-celery-with-beat.sh
    depends_on: *default-depends_on
    environment: *default-environment
    restart: unless-stopped
    volumes:
      - ./uploads:/code/uploads
    networks:
      - default
      - pgsql
  
  migrate:
    image: glitchtip/glitchtip
    depends_on: *default-depends_on
    command: "./manage.py migrate"
    environment: *default-environment
    networks:
      - pgsql
      - default

networks:
  default:
    name: glitchtip
  swag:
    name: swag
    external: true
  pgsql:
    name: pgsql
    external: true

启动就完事了

首次启动后需要 migrate ,这时候 worker 会报错,没事,等 migrate 完成就好了。

登录后注册、创建一个新组织,就可以正常使用了。

代码里使用

仅使用glitchtip

如果单纯用 glitchtip,当成 sentry 来用就行了

import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration

sentry_sdk.init(
    dsn="YOUR-GLITCHTIP-DSN-HERE",
    integrations=[DjangoIntegration()],
    auto_session_tracking=False,
    traces_sample_rate=0.01,
    release="1.0.0",
    environment="production",
)

就这么朴实无华

与 sentry 同时使用

PS:
话说为什么要和sentry同时使用?

sentry_sdk 默认不支持同时初始化多个实例

所以通过一些额外的逻辑来实现这个功能

class GlitchtipSentrySdk:
    def __init__(self, dsn):
        self.client = sentry_sdk.Hub(sentry_sdk.Client(dsn))

    def capture_exception(self, exc):
        with self.client:
            sentry_sdk.capture_exception(exc)

glitchtip = GlitchtipSentrySdk('https://balabalaba@glitchtip.example.com/1')

搞定了

小结

简单体验下来,glitchtip真的简陋很多

不过最基本的错误收集功能还是有的

如果服务器性能不够的话,作为sentry的平替还是不错的。

还有个 highlight 我还没部署上,里面的配置有点复杂。