當前位置:首頁 » 操作系統 » clean演算法

clean演算法

發布時間: 2023-02-06 17:14:46

① 保存失敗 Value at 0 is null. 什麼意思



作者 | 聶曉龍(率鴿)

01 前言

前天回家路上,有輛車強行插到前面的空位,司機大哥暴躁地拍著方向盤吐槽道「加塞最可惡了」,我問「還有更可惡的嗎」,司機大哥淡定說道「不讓自己加塞的」。似乎和我們很類似,我們程序員屆也有這 2 件相輔相成的事:最討厭別人不寫注釋,更討厭讓自己寫注釋。

一段糟糕的代碼,往往大家最低的預期是把注釋寫清楚,最合理的做法通常應該對代碼做優化。如果我們將代碼真正做到了優秀,我們是否還需要注釋?

02 注釋的意義

; **************************************************************************
; * RAMinit Release 2.0 *
; * Copyright (c) 1989-1994 by Yellow Rose Software Co. *
; * Written by Mr. Leijun *
; * Press HotKey to remove all TSR program after this program *
; **************************************************************************
; Removed Softwares by RI:
; SPDOS v6.0F, WPS v3.0F
; Game Busters III, IV
; NETX ( Novell 3.11 )
; PC-CACHE
; Norton Cache
; Microsoft SmartDrv
; SideKick 1.56A
; MOUSE Driver
; Crazy (Monochrome simulate CGA program)
; RAMBIOS v2.0
; 386MAX Version 6.01

注釋是對代碼的解釋和說明,本質目的是為了增強程序的可讀性與可解釋性。注釋會隨著源代碼,在進入預處理器或編譯器處理後會被移除。這是雷布斯 1994 年寫的一段 MASM 匯編代碼,注釋與代碼整體結構都非常清晰。如果說代碼是為了讓機器讀懂我們的指令,那注釋完全就是為了讓我們了解我們自己到底發出了哪些指令。

03 爭議與分歧

注釋的起源非常早,我們甚至已經查閱不到注釋的由來,但現在任何一種語言,甚至幾乎任何一種文本格式都支持各式各樣的注釋形式。

但如何使用注釋,其實一直是一個備受爭論的話題。當我們接手一段『祖傳代碼』時,沒有注釋的感覺簡直讓人抓狂,我們總是希望別人能提供更多的注釋。但軟體屆也有一段神話傳說,叫做『我的代碼像詩一樣優雅』。有注釋的代碼都存在著一些瑕疵,認為足夠完美的代碼是不需要注釋的。

04 壞代碼的救命稻草

The proper use of comments is to compensate for our failure to express ourself in code. -- Robert C. Martin 《Clean Code》 譯:注釋的恰當用法是彌補我們在用代碼表達意圖時遭遇的失敗

Clean Code 的作者 Robert C. Martin 可以說是注釋的極力否定者了,他認為注釋是一種失敗,當我們無法找到不用注釋就能表達自我的方法時,才會使用注釋,任何一次注釋的使用,我們都應該意識到是自己表達能力上的失敗。

PH&V 的系統架構師和負責人 Peter Vogel,同樣也是一名堅定的注釋否定著,他發表了一篇文章 why commenting code is still bad 來表述為代碼添加註釋在某種程度上可能是必要的,但確實沒有價值。

事實上,我們也確實經歷著非常多無價值的注釋,以及完全應由代碼來承擔解釋工作的「職能錯位」的注釋。

01 零注釋

糟糕的代碼加上完全不存在的注釋,我喜歡稱呼它們為『我和上帝之間的秘密』,當然過 2 個月後也可以稱之為『上帝一個人的秘密』。

壓垮程序員最後一根稻草的,往往都是零注釋。可以沒有文檔,可以沒有設計,但如果沒有注釋,我們每一次閱讀都是災難性的。當我們抱怨它一行注釋都沒有時,其實我們是在抱怨我們很難理解代碼想要表達的含義,注釋是直接原因,但根本原因是代碼。

零注釋往往和壞代碼一起生活,「沒有注釋」的吐槽,其實本質上直擊的是那堆歪七扭八的英文字母,到底它們想表達什麼!

02 無用注釋

/**
* returns the last day of the month
* @return the last day of the month
*/
public Date getLastDayOfMonth(Date date) {
Calendar calendar = new GregorianCalendar();
calendar.setTime(date);
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
return calendar.getTime();
}

這是典型的廢話注釋,讀代碼時代碼本身就能很好的表達具體的含義,我們完全不需要看注釋,並且注釋也不會給我們提供更多有效的信息。無用注釋或許是零注釋的另一個極端,我們擔心自己寫的代碼被人所吐槽,於是盡可能去補全注釋,當你為 getLastDayOfMonth() 補一段 get last day of month 的注釋時,恭喜你,你得到了雙倍的代碼。

03 代碼優於注釋

"Comments Do Not Make Up for Bad Code" -- Robert C.Martin 《Clean Code》 譯:注釋不能美化糟糕的代碼

當需要為一段代碼加上注釋時,說明代碼已經不能很好的表達意圖,於是大家開始為這段代碼添加註釋。Robert C.Martin 在 Clean Code 中提出一個觀點:注釋不能美化糟糕的代碼。能用代碼表達的直接用代碼表達,不能用代碼表達的,你再想想,如何能用代碼表達。

復雜的代碼最直接的表現就是不夠直觀、難以理解,加上注釋後往往會清晰很多,但你是願意看這段代碼:

// 判斷是否活躍用戶
if((customer.getLastLoginTime().after(dateUtils.minusDays(new Date(),15)) && customer.getCommentsLast30Days() > 5)
|| orderService.countRecentDaysByCustomer(customer,30) > 1)

還是這段代碼?

if(customer.isActive())

糟糕代碼的存在,通常是我們寫注釋的常見動機之一。這種試圖粉飾可讀性差的代碼的注釋稱之為『拐杖式注釋』,即使大名鼎鼎的 JDK,也存在這樣的拐杖式注釋。

public synchronized void setFormatter(Formatter newFormatter) {
checkPermission();
// Check for a null pointer
newFormatter.getClass();
formatter = newFormatter;
}

這是取自 JDK java.util.logging.Handler 類的 setFormatter 方法,作者為了不讓空指針異常下傳,提前做一次空指針檢查。沒有這段注釋我們完全不知道游離的這句 newFormatter.getClass() 到底要做什麼,這段注釋也充分表達了作者自己也知道這句代碼難以理解,所以他加上了注釋進行說明。但我們完全可以用 Objects.requireNonNull() 來進行替代。同樣的代碼作用,但可讀性可理解性大不一樣,JDK 里的這段代碼,確實讓人遺憾。

04 注釋否定論

"If our programming languages were expressive enough, or if we had the talent to subtly wield those languages to express our intent, we would not need comments very much—perhaps not at all." -- Robert C.Martin 《Clean Code》 譯:若編程語言足夠有表達力,或者我們長於用這些語言來表達意圖,就不那麼需要注釋--也許根本不需要

通過代碼進行闡述,是注釋否定論的核心思想。當你花功夫來想如何寫注釋,讓這段代碼更好的表達含義時,我們更應該重構它,通過代碼來解釋我們的意圖。每一次注釋的編寫,都是對我們代碼表達能力上的差評,提升我們的歸納、表達、解釋能力,更優於通過注釋來解決問題。當代碼足夠優秀時,注釋則是非必須的。並且需求在不斷調整,代碼一定會隨之變動,但注釋可能慢慢被人遺忘,當代碼與注釋不匹配時,將是更大的災難。

05 軟體設計的烏托邦

01 好吧你很優秀

曾經我的確對優秀的代碼不斷鑽研,對代碼本身所蘊含的能量無比堅信。如同當科學代替鬼神論走上歷史舞台時,即使存在有科學解釋不了,我們依然堅信只是科學還需要發展。當代碼別人無法理解時,我會認為是我表述不夠精準,抽象不夠合理,然後去重構去完善。

有一次給老闆 review 代碼,當時老闆提出,「你的代碼缺缺少注釋」,我說不需要注釋,代碼就能自解釋。於是老闆現場讀了一段代碼,「query-customer-list 查詢客戶」、「transfer-customer-to-sales 分發客戶到銷售」、「check-sales-capacity 檢查銷售庫容」,每一個類每一個函數,一個單詞一個單詞往外蹦時,你會發現好像確實都能讀懂,於是老闆回了一個「好吧」。

02 美麗的烏托邦

"'good code is self-documenting' is a delicious myth" -- John Ousterhout《A Philosophy of Software Design》 譯:『好的代碼自解釋』是一個美麗的謊言

在軟體設計中,總有一些軟體工程師所堅信的詩和遠方,有的是大洋彼岸的美好國度,有的或許是虛無縹緲的理想烏托邦。John Ousterhout 教授在 A Philosophy of Software Design 中提到一個觀念,『好的代碼自解釋』是一個美麗的謊言。

我們可以通過選擇更好的變數名,更准確的類與方法,更合理的繼承與派生來減少注釋,但盡快如此,我們還是有非常多的信息無法直接通過代碼來表達。這里的信息,或許不單單只是業務邏輯與技術設計,可能還包括了我們的觀感,我們的體驗,我們的接納程度以及第一印象帶來的首因效應。

06 好代碼的最佳僚機

You might think the purpose of commenting is to 'explain what the code does', but that is just a small part of it.The purpose of commenting is to help the reader know as much as the writer did. -- Dustin Boswell《The Art of Readable Code》 譯:你可能以為注釋的目的是「解釋代碼做了什麼」,但這只是其中很小一部分,注釋的目的是盡量幫助讀者了解得和作者一樣多

如同 John Ousterhout 教授一樣,The Art of Readable Code 的作者 Dustin Boswell,也是一個堅定的注釋支持者。與 Robert C.Martin 類似,Dustin Boswell 同樣認為我們不應該為那些從代碼本身就能快速推斷的事實寫注釋,並且他也反對拐杖式注釋,注釋不能美化代碼。

但 Dustin Boswell 認為注釋的目的不僅解釋了代碼在做什麼,甚至這只是一小部分,注釋最重要的目的是幫助讀者了解得和作者一樣多 。編寫注釋時,我們需要站在讀者的角度,去想想他們知道什麼,這是注釋的核心。這里有非常多的空間是代碼很難闡述或無法闡述的,配上注釋的代碼並非就是糟糕的代碼,相反有些時候,注釋還是好代碼最棒的僚機。

01 更精準表述

There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton 譯:計算機科學中只有兩個難題:緩存失效和命名

Martin Fowler 在他的 TwoHardThings 文章中引用了 Phil Karlton 的一段話,命名一直都是一件非常難的事情,因為我們需要將所有含義濃縮到幾個單詞中表達。很早之前學 Java,接觸到很長的類名是 。可能有人認為只要能將含義准確地表達出來,名字長一些無所謂。那如果我們需要有一段處理有關「一帶一路」的內容,那我們的代碼可能是這樣的:

public class {

他非常准確的表達了含義,但很明顯這不是我們期望的代碼。但如果我們輔以簡單的注釋,代碼會非常清晰,說明了簡稱,也說明了全意,表述更精準。

/**
* 一帶一路
* 絲綢之路經濟帶和21世紀海上絲綢之路
*/
public class OneBeltOneRoad {

02 代碼層次切割

函數抽取是我們經常使用且成本最低的重構方法之一,但並非銀彈。函數並非抽得越細越好,如同分布式系統中,並非無限的堆機器讓每台機器處理的數據越少,整體就會越快。過深的嵌套封裝,會加大我們的代碼閱讀成本,有時我們只需要有一定的層次與結構幫助我們理解就夠了,盲目的抽取封裝是無意義的。

/**
* 客戶列表查詢
*/
public List queryCustomerList(){
// 查詢參數准備
UserInfo userInfo = context.getLoginContext().getUserInfo();
if(userInfo == null || StringUtils.isBlank(userInfo.getUserId())){
return Collections.emptyList();
}
LoginDTO loginDTO = userInfoConvertor.convertUserInfo2LoginDTO(userInfo);
// 查詢客戶信息
List customerSearchList = customerRemoteQueryService.query(loginDTO);
Iterable it = customerSearchList.iterator();
// 排除不合規客戶
while(it.hasNext()){
CustomerSearchVO customerSearchVO = it.next();
if(isInBlackList(customerSearchVO) || isLowQuality(customerSearchVO)){
it.remove();
}
}
// 補充客戶其他屬性信息
batchFillCustomerPositionInfo(customerSearchList);
batchFillCustomerAddressInfo(customerSearchList);
}

其實細看每一處代碼,都很容易讓人理解。但如果是一版沒有注釋的代碼,可能我們會有點頭疼。缺少結構缺少分層,是讓我們大腦第一感觀覺得它很復雜,需要一次性消化多個內容。通過注釋將代碼層次進行切割,是一次抽象層次的劃分。同時也不建議大家不斷去抽象私有方法,這樣代碼會變得非常割裂,並且上下文的背景邏輯、參數的傳遞等等,都會帶來額外的麻煩。

03 母語的力量

其實上述例子,我們更易閱讀,還有一個重要的原因,那就是母語的力量。我們天然所經歷的環境與我們每天所接觸到的事物,讓我們對中文與英文有完全不一樣的感受。我們代碼的編寫本質上是一個將我們溝通中的「中文問題」,翻譯成「英文代碼」來實現的過程。而閱讀代碼的人在做得,是一件將「英文代碼」翻譯成「中文表述」的事情。而這之中經過的環節越多,意思變味越嚴重。

TaskDispatch taskDispatch = TaskDispatchBuilder.newBuilder().withExceptionIgnore().build();
taskDispatch
// 外貿信息
.join(new FillForeignTradeInfoTask(targetCustomer, sourceInfo))
// 國民經濟行業、電商平台、注冊資本
.join(new FillCustOutterInfoTask(targetCustomer, sourceInfo))
// 客戶信息
.join(new (targetCustomer, sourceInfo))
// 客戶擴展信息
.join(new FillCustExtInfoTask(targetCustomer, sourceInfo))
// 收藏屏蔽信息
.join(new FillCollectStatusInfoTask(targetCustomer, sourceInfo, loginDTO()))
// 詳情頁跳轉需要的標簽信息
.join(new FillTagInstanceTask(targetCustomer, sourceInfo, loginDTO()))
// 客戶信息完整度分數
.join(new FillCustomerScoreTask(targetCustomer, sourceInfo))
// 潛客分層完整度
.join(new FillCustomerSegmentationTask(targetCustomer, sourceInfo))
// 填充操作信息
.join(new FillOperationStatusTask(targetCustomer, sourceInfo, loginDTO))
// 認證狀態
.join(new FillAvStatusTask(targetCustomer, loginDTO))
// 客戶地址和組織
.join(new FillCompanyAddressTask(targetCustomer, loginDTO))
// 違規信息
.join(new FillPunishInfoTask(targetCustomer, sourceInfo))
// 填充客戶黑名單信息
.join(new FillCustomerBlackStatusTask(targetCustomer, sourceInfo))
// 填充客戶意願度
.join(new FillCustIntentionLevelTask(targetCustomer, sourceInfo));
// 執行
.execute();

這是一段補齊客戶全數據信息的代碼,雖然每一個英文我們都看得懂,但我們永遠只會第一眼去看注釋,就因為它是中文。並且也因為有這些注釋,這里非常復雜的業務邏輯,我們同樣可以非常清晰的了解到它做了哪些,分哪幾步,如果要優化應該如何處理。這里也建議大家寫中文注釋,注釋是一種說明,越直觀越好,中文的親和力是英文無法比擬的。當然,這條建議並不適合美國程序員。

07 注釋的真正歸屬

01 復雜的業務邏輯

// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if ((beanName)) {
throw new (beanName);
}
// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// Not found -> check parent.
String nameToLookup = originalBeanName(name);
if (args != null) {
// Delegation to parent with explicit args.
return parentBeanFactory.getBean(nameToLookup, args);
}
else {
// No args -> delegate to standard getBean method.
return parentBeanFactory.getBean(nameToLookup, requiredType);
}
}

這是 Spring 中的一段獲取 bean 的代碼,spring 作為容器管理,獲取 bean 的邏輯也非常復雜。對於復雜的業務場景,配上必要的注釋說明,可以更好的理解相應的業務場景與實現邏輯。


截取自:

org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

02 晦澀的演算法公式

/**
* Returns the value obtained by reversing the order of the bits in the
* two's complement binary representation of the specified {@code long}
* value.
*/
public static long reverse(long i) {
// HD, Figure 7-1
i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;
i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;
i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL;
i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL;
i = (i << 48) | ((i & 0xffff0000L) << 16) |
((i >>> 16) & 0xffff0000L) | (i >>> 48);
return i;
}

這是 JDK 中 Long 類中的一個方法,為 reverse 方法添加了足夠多的注釋。對於幾乎沒有改動且使用頻繁的底層代碼,性能的優先順序會高於可讀性。在保證高效的同時,注釋幫助我們彌補了可讀性的短板。


截取自:java.lang.Long#reverse

03 不明所以的常量

/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;

這是 JDK 中 HashMap 的一個常量因子,記錄由鏈表轉向紅黑樹的鏈表長度閾值,超過該長度則鏈表轉為紅黑樹。這里記錄了一個 8,不僅記錄了該常量的用途,也記錄了為什麼我們定義這個值。經常我們會發現我們代碼中存在一個常量等於 3、等於 4,有時我們不知道這些 3 和 4 是干什麼的,有時我們不知道為什麼是 3 和 4。

截取自:java.util.HashMap#TREEIFY_THRESHOLD

04 意料之外的行為

for (int i = 0; i < 3; i++) {
// if task running, invoke only check result ready or not
Result result = bigDataQueryService.queryBysql(sql, token);
if (SUCCESS.equals(result.getStatus())) {
return result.getValue();
}
Thread.sleep(5000);
}

代碼及注釋所示為每 5 秒 check 一下是否有結果返回,遠程服務將觸發與獲取放在了一個介面。沒有注釋我們可能認為這段代碼有問題,代碼表現的含義更像是每 5 秒調用一次,而非每 5 秒 check 一次。為意料之外的行為添加註釋,可以減少對代碼的誤解讀,並向讀者說明必要的背景及邏輯信息。

05 介面對外 API

Checks if a CharSequence is empty (""), null or whitespace only.

Whitespace is defined by {@link Character#isWhitespace(char)}.


* StringUtils.isBlank(null) = true
* StringUtils.isBlank("") = true
* StringUtils.isBlank(" ") = true
* StringUtils.isBlank("bob") = false
* StringUtils.isBlank(" bob ") = false
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is null, empty or whitespace only
public static boolean isBlank(final CharSequence cs) {
final int strLen = length(cs);
if (strLen == 0) {
return true;
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;

return true;

我們經常使用的 StringUtils 工具類中的 isBlank 方法,寫了非常詳情的注釋,不僅包括方法的邏輯,入參的含義,甚至還包括具體示例。我們平常定義的二方庫中的 HSF、HTTP 介面定義,同樣需要有清晰詳盡的注釋,這里的注釋甚至經常會多過你的代碼。

截取自:org.apache.commons.lang3.StringUtils#isBlank

06 法律文件信息

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding right ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

與法律相關的注釋,在開源軟體庫中較經常遇到。涉及到一些版權及著作聲明時,我們需要在源文件頂部放置法律相關注釋。當然,我們不需要將所有法律信息寫到注釋中,如例子中的跳鏈,引用一份標準的外部文檔,會是一個更好的選擇。

08 寫在最後

注釋並不會妨礙你寫出優雅簡潔的代碼,它只是程序固有的一部分而已。我們不用過分在意我們的代碼是否可以脫離注釋,也不需要強調因為我們的代碼符合什麼原則,滿足什麼約定,所以代碼是優秀的注釋是冗餘的。代碼是一門藝術,並不會因為滿足三規九條它就一定完美,因為藝術,是不可衡量的。

參閱書籍


《A Philosophy of Software Design》

《Clean Code》

《The Art of Readable Code》

技 術 好 文

企 業 案 例

② 宏基因組分析筆記之binning

一、宏基因組簡介:

reads→(根據overlap組裝)→ contig重疊群 → (構建454 paired-end庫或illumina meta-paired庫,組裝)→ scaffold → (binning)→ chromosome基因組草圖

Contig N50:Reads拼接後會獲得一些不同長度的Contigs.將所有的Contig長度相加,能獲得一個Contig總長度.然後將所有的Contigs按照從長到短進行排序,如獲得Contig 1,Contig 2,contig 3...………Contig 25.將Contig按照這個順序依次相加,當相加的長度達到Contig總長度的一半時,最後一個加上的Contig長度即為Contig N50.舉例:Contig 1+Contig 2+ Contig 3 +Contig 4=Contig總長度*1/2時,Contig 4的長度即為Contig N50.ContigN50可以作為基因組拼接的結果好壞的一個判斷標准.

Scaffold N50:Scaffold N50與Contig N50的定義類似.Contigs拼接組裝獲得一些不同長度的Scaffolds.將所有的Scaffold長度相加,能獲得一個Scaffold總長度.然後將所有的Scaffolds按照從長到短進行排序,如獲得Scaffold 1,Scaffold 2,Scaffold 3...………Scaffold 25.將Scaffold按照這個順序依次相加,當相加的長度達到Scaffold總長度的一半時,最後一個加上的Scaffold長度即為Scaffold N50.舉例:Scaffold 1+Scaffold 2+ Scaffold3 +Scaffold 4 +Scaffold 5=Scaffold總長度*1/2時,Scaffold 5的長度即為Scaffold N50.Scaffold N50可以作為基因組拼接的結果好壞的一個判斷標准.

二、binning簡介:

宏基因組分箱(Binning)是將宏基因組測序得到的混合了不同生物的序列或序列組裝得到的contigs按物種分開歸類的過程。宏基因組分箱技術有助於獲得不可培養微生物的全基因組序列,獲得新物種的基因組序列和功能,預測未知物種的培養方法等等。

1,統計contig深度

第一列:contigName

第二列:contigLen

第三列:totalAvgDepth

第四列:library1.sorted.bam

第五列:library1.sorted.bam-var

第六列:library2.sorted.bam

第七列:library2.sorted.bam-var

2,用metabat軟體binning

3,CheckM軟體做基因組的質量評估

基因組組裝或者宏基因組binning獲得的基因組草圖,首先需要評估其質量,包括基因組完整度、污染度、序列分布等信息。

https://mp.weixin.qq.com/s/2cggAwQbRRWG9WfjEj0t9Q

三、binning原理

1、binning的依據:

(1)根據核酸組成信息來進行binning:k-mer frequencies(來自同一菌株的序列,其核酸組成是相似的):如根 據核酸使用頻率 (oligonucleotide frequency variations),通常是四核苷酸頻率(tetranucleotide frequency), GC含量 和 必需的單拷貝基因 等

(2)根據豐度信息來進行binning:來自同一個菌株的基因在不同的樣品中 ( 不同時間或不同病理程度 ) 的豐度分布模式是相似的。如,某一細菌中有兩個基因,A和B,它們在該細菌基因組中的拷貝數比例為 A:B = 2:1,則不管在哪個樣品中這種細菌的數量有多少,這兩個基因的豐度比例總是為 2:1。但這種方式需要較大樣本量,一般至少要50個樣本以上,且至少要有2個組能呈現豐度變化 ( 即不同的處理、不同的時間、疾病和健康、或者不同的采樣地點等 ) ,每個組內的生物學重復也要盡量的多。

(3)同時依據核酸組成和豐度變化信息:利用核酸組成信息和豐度差異綜合計算距離矩陣,既能保證binning效果,也能相對節約計算資源,現在比較主流的binning軟體大多是NCA演算法。

(4)根據基因組甲基化模式:不同的細菌,其基因組甲基化模式不同,平均一種細菌有3種特意的甲基化 motif。MGEs (mobile genetic elements) 中含有 MTase 基因,其基因水平轉移是細菌甲基化組多樣性的驅動因素。雖然 MGEs 在不同個體的拷貝數不同,但是都存在,因此具有相同 MGEs 的細菌個體,其總遺傳物質(包括染色體和 MGEs )都會受到相同的MTase的作用而得到相同的甲基化模式。

2、binning

原始的clean reads,還是從組裝成的contig,還是從預測到的gene,都可以binning,暫且分為reads binning, contig binning和 genes binning。應用最廣泛的就是基於genes binning 和 contig binning

四、binning後續分析

關聯分析

即通過binning得到的bins(暫且簡稱為bins,更確切的說是strain-level clusters 或strain-level taxonomic units)可以進行宏基因組關聯分析以及多組學聯合分析,將特定功能代謝產物與特定物種、特定基因進行關聯研究,推動其因果機制的探究,為疾病監控、環境監測提供了菌株水平的生物靶標。

單菌組裝

通過對binning得到的bins進行後續組裝,可以得到很多不能在實驗室里培養的細菌、古菌、病毒的基因組草圖,然後根據單菌組裝結果進行菌株水平的基因和功能注釋、比較基因組分析、進化分析等,使我們得以洞察這些無法在實驗室培養獲得的菌株的生態適應機制,營養互作機制和新陳代謝功能等,可以研究在生態環境和復雜疾病中起重要作用的菌種以及致病菌和宿主的互作機制及其微進化機制。

參考:

https://www.jianshu.com/p/66ab14988a74

https://www.jianshu.com/p/f010020a2859

https://mp.weixin.qq.com/s/2cggAwQbRRWG9WfjEj0t9Q

http://www.pinlue.com/article/2018/09/1804/497268180444.html

https://www.jianshu.com/p/117441ac6eb8

③ 如何對混合型數據做聚類分析

如何對混合型數據做聚類分析
利用聚類分析,我們可以很容易地看清數據集中樣本的分布情況。以往介紹聚類分析的文章中通常只介紹如何處理連續型變數,這些文字並沒有過多地介紹如何處理混合型數據(如同時包含連續型變數、名義型變數和順序型變數的數據)。本文將利用 Gower 距離、PAM(partitioning around medoids)演算法和輪廓系數來介紹如何對混合型數據做聚類分析。
R語言
本文主要分為三個部分:
距離計算
聚類演算法的選擇
聚類個數的選擇
為了介紹方便,本文直接使用 ISLR 包中的 College 數據集。該數據集包含了自 1995 年以來美國大學的 777 條數據,其中主要有以下幾個變數:
連續型變數
錄取率
學費
新生數量
分類型變數
公立或私立院校
是否為高水平院校,即所有新生中畢業於排名前 10% 高中的新生數量佔比是否大於 50%
本文中涉及到的R包有:
In [3]:
set.seed(1680) # 設置隨機種子,使得本文結果具有可重現性
library(dplyr)
library(ISLR)
library(cluster)
library(Rtsne)
library(ggplot2)

Attaching package: 『dplyr』

The following objects are masked from 『package:stats』:

filter, lag

The following objects are masked from 『package:base』:

intersect, setdiff, setequal, union

構建聚類模型之前,我們需要做一些數據清洗工作:
錄取率等於錄取人數除以總申請人數
判斷某個學校是否為高水平院校,需要根據該學校的所有新生中畢業於排名前 10% 高中的新生數量佔比是否大於 50% 來決定

In [5]:

college_clean <- College %>%
mutate(name = row.names(.),
accept_rate = Accept/Apps,
isElite = cut(Top10perc,
breaks = c(0, 50, 100),
labels = c("Not Elite", "Elite"),
include.lowest = TRUE)) %>%
mutate(isElite = factor(isElite)) %>%
select(name, accept_rate, Outstate, Enroll,
Grad.Rate, Private, isElite)

glimpse(college_clean)

Observations: 777
Variables: 7
$ name (chr) "Abilene Christian University", "Adelphi University", "...
$ accept_rate (dbl) 0.7421687, 0.8801464, 0.7682073, 0.8369305, 0.7564767, ...
$ Outstate (dbl) 7440, 12280, 11250, 12960, 7560, 13500, 13290, 13868, 1...
$ Enroll (dbl) 721, 512, 336, 137, 55, 158, 103, 489, 227, 172, 472, 4...
$ Grad.Rate (dbl) 60, 56, 54, 59, 15, 55, 63, 73, 80, 52, 73, 76, 74, 68,...
$ Private (fctr) Yes, Yes, Yes, Yes, Yes, Yes, Yes, Yes, Yes, Yes, Yes,...
$ isElite (fctr) Not Elite, Not Elite, Not Elite, Elite, Not Elite, Not...
距離計算
聚類分析的第一步是定義樣本之間距離的度量方法,最常用的距離度量方法是歐式距離。然而歐氏距離只適用於連續型變數,所以本文將採用另外一種距離度量方法—— Gower 距離。
Gower 距離
Gower 距離的定義非常簡單。首先每個類型的變數都有特殊的距離度量方法,而且該方法會將變數標准化到[0,1]之間。接下來,利用加權線性組合的方法來計算最終的距離矩陣。不同類型變數的計算方法如下所示:
連續型變數:利用歸一化的曼哈頓距離
順序型變數:首先將變數按順序排列,然後利用經過特殊調整的曼哈頓距離
名義型變數:首先將包含 k 個類別的變數轉換成 k 個 0-1 變數,然後利用 Dice 系數做進一步的計算
優點:通俗易懂且計算方便
缺點:非常容易受無標准化的連續型變數異常值影響,所以數據轉換過程必不可少;該方法需要耗費較大的內存
利用 daisy 函數,我們只需要一行代碼就可以計算出 Gower 距離。需要注意的是,由於新生入學人數是右偏變數,我們需要對其做對數轉換。daisy 函數內置了對數轉換的功能,你可以調用幫助文檔來獲取更多的參數說明。

In [6]:

# Remove college name before clustering

gower_dist <- daisy(college_clean[, -1],
metric = "gower",
type = list(logratio = 3))

# Check attributes to ensure the correct methods are being used
# (I = interval, N = nominal)
# Note that despite logratio being called,
# the type remains coded as "I"

summary(gower_dist)

Out[6]:

301476 dissimilarities, summarized :
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.0018601 0.1034400 0.2358700 0.2314500 0.3271400 0.7773500
Metric : mixed ; Types = I, I, I, I, N, N
Number of objects : 777

此外,我們可以通過觀察最相似和最不相似的樣本來判斷該度量方法的合理性。本案例中,聖托馬斯大學和約翰卡羅爾大學最相似,而俄克拉荷馬科技和藝術大學和哈佛大學差異最大。

In [7]:

gower_mat <- as.matrix(gower_dist)

# Output most similar pair

college_clean[
which(gower_mat == min(gower_mat[gower_mat != min(gower_mat)]),
arr.ind = TRUE)[1, ], ]

Out[7]:

In [8]:

# Output most dissimilar pair

college_clean[
which(gower_mat == max(gower_mat[gower_mat != max(gower_mat)]),
arr.ind = TRUE)[1, ], ]

Out[8]:

聚類演算法的選擇

現在我們已經計算好樣本間的距離矩陣,接下來需要選擇一個合適的聚類演算法,本文採用 PAM(partioniong around medoids)演算法來構建模型:

PAM 演算法的主要步驟:

隨機選擇 k 個數據點,並將其設為簇中心點
遍歷所有樣本點,並將樣本點歸入最近的簇中
對每個簇而言,找出與簇內其他點距離之和最小的點,並將其設為新的簇中心點
重復第2步,直到收斂

該演算法和 K-means 演算法非常相似。事實上,除了中心點的計算方法不同外,其他步驟都完全一致 。

優點:簡單易懂且不易受異常值所影響
缺點:演算法時間復雜度為 O(n2)O(n2)

聚類個數的選擇

我們將利用輪廓系數來確定最佳的聚類個數,輪廓系數是一個用於衡量聚類離散度的內部指標,該指標的取值范圍是[-1,1],其數值越大越好。通過比較不同聚類個數下輪廓系數的大小,我們可以看出當聚類個數為 3 時,聚類效果最好。

In [9]:

# Calculate silhouette width for many k using PAM

sil_width <- c(NA)

for(i in 2:10){

pam_fit <- pam(gower_dist,
diss = TRUE,
k = i)

sil_width[i] <- pam_fit$silinfo$avg.width

}

# Plot sihouette width (higher is better)

plot(1:10, sil_width,
xlab = "Number of clusters",
ylab = "Silhouette Width")
lines(1:10, sil_width)

聚類結果解釋
描述統計量

聚類完畢後,我們可以調用 summary 函數來查看每個簇的匯總信息。從這些匯總信息中我們可以看出:簇1主要是中等學費且學生規模較小的私立非頂尖院校,簇2主要是高收費、低錄取率且高畢業率的私立頂尖院校,而簇3則是低學費、低畢業率且學生規模較大的公立非頂尖院校。

In [18]:

pam_fit <- pam(gower_dist, diss = TRUE, k = 3)

pam_results <- college_clean %>%
dplyr::select(-name) %>%
mutate(cluster = pam_fit$clustering) %>%
group_by(cluster) %>%
do(the_summary = summary(.))

print(pam_results$the_summary)

[[1]]
accept_rate Outstate Enroll Grad.Rate Private
Min. :0.3283 Min. : 2340 Min. : 35.0 Min. : 15.00 No : 0
1st Qu.:0.7225 1st Qu.: 8842 1st Qu.: 194.8 1st Qu.: 56.00 Yes:500
Median :0.8004 Median :10905 Median : 308.0 Median : 67.50
Mean :0.7820 Mean :11200 Mean : 418.6 Mean : 66.97
3rd Qu.:0.8581 3rd Qu.:13240 3rd Qu.: 484.8 3rd Qu.: 78.25
Max. :1.0000 Max. :21700 Max. :4615.0 Max. :118.00
isElite cluster
Not Elite:500 Min. :1
Elite : 0 1st Qu.:1
Median :1
Mean :1
3rd Qu.:1
Max. :1

[[2]]
accept_rate Outstate Enroll Grad.Rate Private
Min. :0.1545 Min. : 5224 Min. : 137.0 Min. : 54.00 No : 4
1st Qu.:0.4135 1st Qu.:13850 1st Qu.: 391.0 1st Qu.: 77.00 Yes:65
Median :0.5329 Median :17238 Median : 601.0 Median : 89.00
Mean :0.5392 Mean :16225 Mean : 882.5 Mean : 84.78
3rd Qu.:0.6988 3rd Qu.:18590 3rd Qu.:1191.0 3rd Qu.: 94.00
Max. :0.9605 Max. :20100 Max. :4893.0 Max. :100.00
isElite cluster
Not Elite: 0 Min. :2
Elite :69 1st Qu.:2
Median :2
Mean :2
3rd Qu.:2
Max. :2

[[3]]
accept_rate Outstate Enroll Grad.Rate Private
Min. :0.3746 Min. : 2580 Min. : 153 Min. : 10.00 No :208
1st Qu.:0.6423 1st Qu.: 5295 1st Qu.: 694 1st Qu.: 46.00 Yes: 0
Median :0.7458 Median : 6598 Median :1302 Median : 54.50
Mean :0.7315 Mean : 6698 Mean :1615 Mean : 55.42
3rd Qu.:0.8368 3rd Qu.: 7748 3rd Qu.:2184 3rd Qu.: 65.00
Max. :1.0000 Max. :15516 Max. :6392 Max. :100.00
isElite cluster
Not Elite:199 Min. :3
Elite : 9 1st Qu.:3
Median :3
Mean :3
3rd Qu.:3
Max. :3

PAM 演算法的另一個優點是各個簇的中心點是實際的樣本點。從聚類結果中我們可以看出,聖弗朗西斯大學是簇1 的中心點,巴朗德學院是簇2 的中心點,而密歇根州州立大學河谷大學是簇3 的中心點。

In [19]:

college_clean[pam_fit$medoids, ]

Out[19]:

可視化方法

t-SNE 是一種降維方法,它可以在保留聚類結構的前提下,將多維信息壓縮到二維或三維空間中。藉助t-SNE我們可以將 PAM 演算法的聚類結果繪制出來,有趣的是私立頂尖院校和公立非頂尖院校這兩個簇中間存在一個小聚類簇。

In [22]:

tsne_obj <- Rtsne(gower_dist, is_distance = TRUE)

tsne_data <- tsne_obj$Y %>%
data.frame() %>%
setNames(c("X", "Y")) %>%
mutate(cluster = factor(pam_fit$clustering),
name = college_clean$name)

ggplot(aes(x = X, y = Y), data = tsne_data) +
geom_point(aes(color = cluster))

進一步探究可以發現,這一小簇主要包含一些競爭力較強的公立院校,比如弗吉尼亞大學和加州大學伯克利分校。雖然無法通過輪廓系數指標來證明多分一類是合理的,但是這 13 所院校的確顯著不同於其他三個簇的院校。

In [25]:

tsne_data %>%
filter(X > 15 & X < 25,
Y > -15 & Y < -10) %>%
left_join(college_clean, by = "name") %>%
collect %>%
.[["name"]]

Out[25]:

『Kansas State University』
『North Carolina State University at Raleigh』
『Pennsylvania State Univ. Main Campus』
『SUNY at Buffalo』
『Texas A&M Univ. at College Station』
『University of Georgia』
『University of Kansas』
『University of Maryland at College Park』
『University of Minnesota Twin Cities』
『University of Missouri at Columbia』
『University of Tennessee at Knoxville』
『University of Texas at Austin』

④ 數字圖像處理clean演算法的MATLAB代碼

圖像去噪是數字圖像處理中的重要環節和步驟。去噪效果的好壞直接影響到後續的圖像處理工作如圖像分割、邊緣檢測等。圖像信號在產生、傳輸過程中都可能會受到雜訊的污染,一般數字圖像系統中的常見雜訊主要有:高斯雜訊(主要由阻性元器件內部產生)、椒鹽雜訊(主要是圖像切割引起的黑圖像上的白點雜訊或光電轉換過程中產生的泊松雜訊)等; 
目前比較經典的圖像去噪演算法主要有以下三種: 
均值濾波演算法:也稱線性濾波,主要思想為鄰域平均法,即用幾個像素灰度的平均值來代替每個像素的灰度。有效抑制加性雜訊,但容易引起圖像模糊,可以對其進行改進,主要避開對景物邊緣的平滑處理。 
中值濾波:基於排序統計理論的一種能有效抑制雜訊的非線性平滑濾波信號處理技術。中值濾波的特點即是首先確定一個以某個像素為中心點的鄰域,一般為方形鄰域,也可以為圓形、十字形等等,然後將鄰域中各像素的灰度值排序,取其中間值作為中心像素灰度的新值,這里領域被稱為窗口,當窗口移動時,利用中值濾波可以對圖像進行平滑處理。其演算法簡單,時間復雜度低,但其對點、線和尖頂多的圖像不宜採用中值濾波。很容易自適應化。 Wiener維納濾波:使原始圖像和其恢復圖像之間的均方誤差最小的復原方法,是一種自適應濾波器,根據局部方差來調整濾波器效果。對於去除高斯雜訊效果明顯。 
實驗一:均值濾波對高斯雜訊的效果 
I=imread('C:\Documents and Settings\Administrator\桌面\1.gif');%讀取圖像

⑤ JVM的垃圾演算法有哪幾種

一、垃圾收集器概述

如上圖所示,垃圾回收演算法一共有7個,3個屬於年輕代、三個屬於年老代,G1屬於橫跨年輕代和年老代的演算法。

JVM會從年輕代和年老代各選出一個演算法進行組合,連線表示哪些演算法可以組合使用

二、各個垃圾收集器說明

1、Serial(年輕代)

  • 年輕代收集器,可以和Serial Old、CMS組合使用

  • 採用復制演算法

  • 使用單線程進行垃圾回收,回收時會導致Stop The World,用戶進程停止

  • client模式年輕代默認演算法

  • GC日誌關鍵字:DefNew(Default New Generation)

  • 圖示(Serial+Serial Old)

    7、G1

  • G1收集器由於沒有使用過,所以從網上找了一些教程供大家了解

  • 並行與並發

  • 分代收集

  • 空間整合

  • 可預測的停頓

熱點內容
騰訊招聘php 發布:2024-04-25 15:17:02 瀏覽:444
雲伺服器多個公網ip搭建 發布:2024-04-25 15:13:15 瀏覽:845
phpmysqlupdate 發布:2024-04-25 15:08:15 瀏覽:253
隨時解壓 發布:2024-04-25 14:58:11 瀏覽:68
三率源碼 發布:2024-04-25 14:42:41 瀏覽:468
javastring轉string數組 發布:2024-04-25 14:42:10 瀏覽:137
飢荒如何更改密碼 發布:2024-04-25 14:42:02 瀏覽:744
百度雲如何解壓zip 發布:2024-04-25 14:38:57 瀏覽:571
母豬怎麼配置最好 發布:2024-04-25 14:35:47 瀏覽:76
php按鈕代碼 發布:2024-04-25 14:32:10 瀏覽:726