Codewars | Isograms

檢查字串是否有重複字母

Johnny Fang
11 min readOct 15, 2023

最後更新時間:2023–10–15

只要是這個主題我就用同一張圖,乾淨衛生不油膩 | 圖片來源:Codewars

前言

請直接看這段前言,我想講的是一模一樣的內容,本文架構也跟那篇相去不遠。

在題目開始之前,有看過 isogram 這個單字嗎?

如果你也在學程式,不知道你有沒有一種感覺,就是常常會遇到一些奇怪的英文單字,所謂奇怪就是不常見(也不排除是自己孤陋寡聞),還記得我遇到的第一個奇怪單字就是 Boolean(布林值),從來沒用過,又例如在這篇提到的 recursion,這個字也不太會用到,而今天標題就屬於這種奇怪單字,依照本部落格慣性,接下來要幹嘛?沒錯,就是說文解字一下(自己回答)。

首先,查了一下奇摩字典 isogram 這單字是個啥東東:

【氣】等值線

ok,看起來是類似氣象學中用來統計氣溫、濕度之類的線圖,或是像以前地理課學過的等高線圖,接著繼續查劍橋與牛津兩部字典卻發現沒收錄這個字,於是好奇去查字首字根,很明顯這個字應該可以拆成 iso + gram:

  • iso:有 equal 的意思,例如 isobar(等壓線,怎麼覺得跟 isogram 很像)、isometric(等量的)、isothermal(等溫的)、isotropic(等向的,似乎是個物理術語),發現都是我不熟的字
  • gram:有 letter、written 意思,像是跟文字有關或具象化地寫出來,這個應該大家就比較熟了,例如 diagram(圖表)、telegram(電報,即遠距離傳輸的文字),還有很多人在用的 Instagram,產品名稱大概有一種即時圖像的概念

嗯……不對啊,我怎麼還是看不出來這跟題目有什麼關係,根據題目闡述(待會下面會看到),isogram 意思是「沒有重複字母的單字」欸,於是查了一下 wiki,上面寫說這個字有兩種意思,一個就是上面說的等值線,另一個則是:

A word or phrase in which each letter occurs the same number of times.

哦哦!?好像有點感覺了,再 google 一下發現這個網站有個說明:

In morphology and verbal play, an isogram is a word with no repeating letters (such as ambidextrously) or, more broadly, a word in which the letters occur an equal number of times. It is also known as a non-pattern word.

ok,先不管這段話夾雜了幾個奇怪單字,大意是說 isogram 為「沒有重複字母的單字」或「每個字母出現同樣次數的單字」

好!這樣就串得起來了,每個字母出現同樣次數,那就有 iso 意味了,而 gram 本來就是 letter 的意思,因此 isogram 這樣子解釋就合理了。如果請你直接想有哪些英文單字是 isogram 第一個會想到什麼?我自己是想到 cat 啦,哈哈哈超爛,沒關係,我們請 AI 來列幾個 isogram 吧,會有兩種情境,一種是字母只出現 1 次,另一種則是出現同樣次數(超過 1 次)。

字母只出現 1 次,其實可以找到滿多的(像 word 本身就是,字母少的很容易就會是 isogram):

  • unique(獨特的)
  • diverge(分叉)
  • postman(郵差)
  • cabin(客艙)
  • quiz(測驗)

字母出現同樣次數(超過 1 次):

  • reappear(再次出現,這個字本身還真貼切這個情境)
  • noon(中午)
  • deed(行為)

第二種情境可能真的很少,因為 AI 列不到 5 個而且瘋狂給出錯誤答案,就不逼它了(話說我用它這個字會不會以後 AI 統治世界時找我算帳,好啦他或她都行)。後來找到一個 repo,其實只能算一張表,列出很多 isogram,不過看了一下幾乎都是第一種情境,只能說作者很閒,啊不是,跟我一樣喜歡研究怪怪的事,有興趣可以點進連結看看。

打到這邊突然驚覺這段篇幅也佔太多了吧,趕緊拉回主軸,這篇是要解題啦XD

今日分享題目:Isograms

⭐ 基本資訊

⭐ 題目的原文內容

An isogram is a word that has no repeating letters, consecutive or non-consecutive. Implement a function that determines whether a string that contains only letters is an isogram. Assume the empty string is an isogram. Ignore letter case.

Example: (Input → Output)

“Dermatoglyphics” → true
“aba” → false
“moOse” → false (ignore letter case)

⭐ 題目說明

就是要判斷該字串是否為 isogram,忽略大小寫,回傳值為布林值。

我的初次寫法

⭐ 思路

  • 由於不用判斷大小寫,所以先將字串都變成小寫再變成陣列
  • 變成陣列後利用 sort() 排序,一樣的元素自然會相鄰,於是只要檢查是否有任兩個相鄰元素相同即可
  • 先立個 flag,預設為 true
  • 接著針對陣列每個元素(也就是原字串每個字元)檢查,若該元素等於後面一個元素,則 flag 會變成 false,因為代表字串至少就有兩個字元一樣
  • 最後將 flag 的值回傳即為答案

⭐ 程式碼 — 原始版

function isIsogram(str){
let strSorted = str.toLowerCase().split('').sort()
let ans = true

strSorted.forEach((e,i) => {
if ( e === strSorted[i+1]) {
ans = false
}
})

return ans
}

⭐ 程式碼 — 註解版

function isIsogram(str){
// 將字串轉換成陣列,全變成小寫且排序
let strSorted = str.toLowerCase().split('').sort()
// 立個 flag
let ans = true
// 依序檢查相鄰元素是否相等
strSorted.forEach((e,i) => {
if ( e === strSorted[i+1]) {
// 若相等則改變 flag 的值
ans = false
}
})
return ans
}

⭐ 其他說明

sort() 是個坑,要花不少時間理解,有興趣可以看:陣列方法 sort() 簡介與資源整理。這題其實不用那麼複雜,只要知道 sort() 會幫你排序,甚至連它怎麼排都不用知道,因為排序沒意外會把一樣的值排在一起沒錯吧,那我這邊只需要確認是否有任兩個相鄰的值相同,其他的不需要,因此這邊直接寫 sort() 即可不需考慮其他事。

我的今日寫法

上面那個初次寫法大約是距離本篇文章撰寫當下約 2 個月前寫的,於是寫本文的早上為了醒醒腦想說那不要用 sort(),立馬靠直覺寫一個,於是用了另一個方法。

⭐ 思路

  • 一樣先立個 flag,接著把字串變成小寫並轉成陣列
  • 先宣告一個空陣列,檢測用
  • 將原陣列元素依序檢查,如果檢測陣列不包含該值,則將元素放入檢測陣列,而如果檢測陣列已包含該值,代表字串字元有重複,因此將 flag 改為 false,並且就不用再檢查下去了直接 return
  • 最後回傳 flag 的值

⭐ 程式碼 — 原始版

function isIsogram(str){
let ans = true
let strArray = str.toLowerCase().split('')
let testArray = []

strArray.forEach(e => {
if(!testArray.includes(e)) {
testArray.push(e)
} else {
ans = false
return
}
})

return ans
}

⭐ 程式碼 — 註解版

function isIsogram(str){
// 立 flag
let ans = true
// 處理字串,變成陣列
let strArray = str.toLowerCase().split('')
// 弄個陣列來檢測元素是否重複
let testArray = []
strArray.forEach(e => {
// 若不包含則放入檢測陣列
if(!testArray.includes(e)) {
testArray.push(e)
} else {
// 若檢測陣列有了,則代表字元重複,將 flag 改為 false
ans = false
// 跳出 forEach()
return
}
})
return ans
}

如果你看得很仔細的話其實會發現上面那段程式碼有一行是沒啥屁用的XD
發現了嗎?
答案是,ans = false 後面那行 return
我原本想法就像註解打的,若已確認字元有重複其實就可以不用再繼續檢查下去,因此很直覺地寫了個 return,可是問題在於,return 掉的是該次迭代,整個 forEach() 還是會繼續進行直到將每個元素都檢查過,如果不信的話可以直接複製下面這段到 IDE 執行看會印出什麼:

function isIsogram(str){
let ans = true
let strArray = str.toLowerCase().split('')
let testArray = []

strArray.forEach(e => {
if(!testArray.includes(e)) {
testArray.push(e)
} else {
ans = false
return
}
console.log(testArray)
})

return ans
}

因此,原寫法雖可解題,但若要達到提前 return 的效果可以利用 for loop 改寫成這樣:

function isIsogram(str){
let ans = true;
let strArray = str.toLowerCase().split('');
let testArray = [];

for (let i = 0; i < strArray.length; i++) {
let e = strArray[i];
if (!testArray.includes(e)) {
testArray.push(e);
} else {
ans = false;
break; // 立即終止迴圈,後面的不用再檢查了
}
}

return ans;
}

其他看到不錯的解法

1️⃣ 利用 Set()

程式碼:

function isIsogram(str){
return new Set(str.toUpperCase()).size == str.length;
}

很帥的解法,一行解決,先說我對 Set() 不熟,大概瞭解而已,以後可以研究研究為它寫一篇。這邊簡單說一下這段程式碼做了些什麼:

  1. str.toUpperCase():先將字串全部轉換成大寫,我自己是習慣小寫
  2. new Set(str.toUpperCase()):這步是重點,將字串轉換為一個 Set 物件。由於 Set 只儲存唯一值,重複的字母將只被計算一次
  3. new Set(str.toUpperCase()).size:這一部分會計算 Set 中唯一字母的數量
  4. new Set(str.toUpperCase()).size == str.length:最後,通過比較 Set 的大小(即唯一字母的數量)和原始字串長度即可判斷字串中是否有重複的字母。如果大小相同,代表字串中的字母都是唯一的,這個表達式就會返回 true,而若大小不同,代表有重複字母所以 Set 的 size 會比原字串長度小,也就返回 false

2️⃣ 利用正規表達式

程式碼:

function isIsogram(str){ 
return !/(\w).*\1/i.test(str)
}

想知道這行在幹嘛嗎?我懶得研究哈哈,如果對正規表達式有興趣可以看:正規表達式 1(Regular Expression,簡寫 Regex 或 RegExp) — 淺談。我在解題時都不會用 Regex 去解,因為 Regex 寫法不太直覺而且我也不熟,當初研究過後就把它列入「要用時再去查」的東西XD,但如果有辦法直接這樣寫出來且成功解題還是滿酷的。

結尾

寫完後覺得有點想笑,竟然花了不少篇幅在說 isogram 這個字🤣,而且整篇長度有點超過預設的小品文型式,這應該算是老毛病?常常寫著寫著就岔題但又想把研究內容與各位分享,確實應該偶爾檢討一下。

--

--

Johnny Fang

把 Medium 當 Notion 用,寫一下 coding 學習筆記 | email: johnny781222@gmail.com | LinkedIn: www.linkedin.com/in/johnny-fang-9356b2156 | Discord 使用者名稱:johnnyfang