第 103 講:C# 3 之查詢表達(dá)式(七):LINQ 階段性練習(xí)(一)
咱們今天來做做練習(xí)題。學(xué)了很多的 LINQ 的知識(shí)了,關(guān)鍵字就算是告一段落。因?yàn)閮?nèi)容繁多,所以咱們搞一個(gè)練習(xí)題的專題。所有題目的答案都會(huì)寫在當(dāng)前部分的最后一點(diǎn)的地方,方便翻閱,但不建議做題期間進(jìn)行參考。
題目
請(qǐng)給出如下的例子需要滿足的查詢表達(dá)式。
示例:現(xiàn)在有一個(gè)集合 int[] numbers
,包含一系列元素。先要獲取其中的所有正奇數(shù)(或者叫單數(shù)),請(qǐng)寫出查詢表達(dá)式。
示例題答案:
解答:這個(gè)問題不大,就不展開說明了。注意取模運(yùn)算符 %
的結(jié)果和被除數(shù)的正負(fù)性一致,所以 number % 2 == 1
包含了對(duì) number > 0
的判斷過程,因?yàn)槿∧_\(yùn)算的結(jié)果比較是和 1 這個(gè)正整數(shù)在比較,而如果是負(fù)奇數(shù)的話,那么取模運(yùn)算結(jié)果應(yīng)該是 -1 而不是 1,此時(shí)就不再滿足條件 number % 2 == 1
,因而會(huì)被篩選掉。
下面是練習(xí)題。一共有 8?個(gè)題。
(容易)題目 1:給出一個(gè)集合
int[] numbers
,包含至少兩個(gè)元素,而且互相不重復(fù)?,F(xiàn)在要獲取其中任意兩個(gè)數(shù)字的和為 17 的這對(duì)數(shù)字,請(qǐng)寫出查詢表達(dá)式。(一般)題目 2:給出一個(gè)集合
int[] numbers
,包含至少兩個(gè)元素,可能有相同數(shù)字,也可能數(shù)字之間不相同?,F(xiàn)在要獲取所有其中任取的兩個(gè)數(shù)字之間的乘積結(jié)果,請(qǐng)寫出查詢表達(dá)式。(一般)題目 3:給出一個(gè)集合
string[] words
,請(qǐng)給出所有單詞里用到的所有字母的使用個(gè)數(shù)的情況,并按出現(xiàn)個(gè)數(shù)進(jìn)行升序排序。(困難)題目 4:請(qǐng)使用查詢表達(dá)式得到星期一到星期天這七天的英語單詞(Monday、Tuesday 等)。不用考慮國際化,換句話說,外國有拿周日當(dāng)成第一天的,中國用的是周一當(dāng)?shù)谝惶斓?。咱們不考慮誰第一天這個(gè)問題,只需要你能夠得到合適的這七個(gè)單詞的正確結(jié)果即可。
(困難)題目 5:請(qǐng)使用查詢表達(dá)式來打亂一個(gè)序列
int[] numbers
。(一般)題目 6:給定一個(gè)集合
string[] words
,請(qǐng)使用查詢表達(dá)式給出這個(gè)單詞序列里只出現(xiàn)了一次的單詞。(困難)題目 7:給定一個(gè)集合
string[] words
,請(qǐng)使用查詢表達(dá)式給出這個(gè)單詞序列里全大寫的單詞。比如說單詞序列是{ "abc", "Abc", "ABC" }
,那么整個(gè)序列就只有第三個(gè)元素"ABC"
是全大寫字母的單詞,那么查詢表達(dá)式找的就是這樣的單詞。(極難)題目 8:給定一個(gè)鋸齒數(shù)組
int[][] matrix
,請(qǐng)將其當(dāng)成矩陣,將其進(jìn)行轉(zhuǎn)置。請(qǐng)使用查詢表達(dá)式來表示轉(zhuǎn)置后的序列。所謂的轉(zhuǎn)置就是行列變換:將每一行改成每一列的形式。
答案
題目 1
題目 2
本題用到了一個(gè)不是很好想到的方法 Enumerable.Range
。該方法可以允許用戶產(chǎn)生一個(gè)序列,序列的開始數(shù)值是第一個(gè)參數(shù),第二個(gè)參數(shù)則表示從這剛才給的第一個(gè)參數(shù)的數(shù)值開始,一共要迭代多少個(gè)數(shù)字。比如說 Enumerable.Range(0, 3)
就表示從開始,迭代 0、1、2 這三個(gè)數(shù)字;Enumerable.Range(3, 4)
就表示從 3 開始,迭代 3、4、5、6 這四個(gè)數(shù)字。
通過這樣的笛卡爾積,我們可以完成 i
和 j
的類似兩層 for
循環(huán)的效果。注意我們給出的笛卡爾積的限制范圍,第一個(gè)參數(shù)只需要從 0 到“數(shù)組長度 - 2”即可。
numbers.Length - 1
作為第二個(gè)參數(shù)表示的是迭代的總元素個(gè)數(shù),而這個(gè)數(shù)值是數(shù)組總元素?cái)?shù)量還少一個(gè),說明我們并未把數(shù)組整個(gè)序列遍歷完成,而還有一個(gè)元素并未被遍歷到,畢竟第一個(gè)參數(shù)的 0 要求我們迭代的初始是從 0 開始的
而第二個(gè)變量 j
則需要避免和 i
重復(fù),因此故意選取 i + 1
作為開始迭代的初始數(shù)值。注意,乘積是滿足交換律的,換句話說就是兩個(gè)數(shù)字相乘,誰乘以誰結(jié)果都是一樣的。然后,迭代的總元素?cái)?shù)量應(yīng)為“長度 - 1 - i”。這個(gè)式子需要理解為“長度 - (i + 1)”,是因?yàn)槲覀兊诙€(gè)變量 j
必須要迭代到數(shù)組的末端,而第一個(gè)參數(shù)我們控制的時(shí)候是迭代到“長度 - 2”的,所以并未考慮最后一個(gè)元素,因此我們需要保證第二個(gè)參數(shù)要能夠到這里去。i
越大,我們遍歷的元素?cái)?shù)量就得越少,這是為了保證數(shù)組越界的問題。比如 i
是 0 的時(shí)候,式子“長度 - i - 1”就等于是“長度 - 1”,而初始迭代的位置是 i + 1
,此時(shí) i
為 0 所以就是 1,因此 j
的迭代范圍是從 1 開始,一直能夠到“長度 - 1”的位置上去。這點(diǎn)要搞清楚。
最后,因?yàn)?i
和 j
已經(jīng)保證了不重復(fù),所以我們直接將 i
和 j
視為遍歷的索引,然后將其放進(jìn)索引器里參與運(yùn)算,就有了 numbers[i] * numbers[j]
的寫法,這就可以得到最終結(jié)果了。
題目 3
題目 4
答案 1 用的是 Enum.GetValues
方法獲取一個(gè)枚舉類型的字段,來迭代;而答案 2 則使用 Enumerable.Range
方法獲取一個(gè)從 0 到 6 的完整數(shù)字序列,然后執(zhí)行對(duì) DayOfWeek
這個(gè)枚舉類型的強(qiáng)制轉(zhuǎn)換(整數(shù)和枚舉類型可以互相轉(zhuǎn)換),最后輸出結(jié)果。如果你實(shí)在是不知道 Enumerable.Range
的話,也可以寫成數(shù)組:new[] { 0, 1, 2, 3, 4, 5, 6 }
。
題目 5
本題難度比較大。orderby
里是可以跟一個(gè)跟 numbers
序列里元素?zé)o關(guān)的東西作為排序依據(jù)的。我順勢(shì)就寫成了隨機(jī)數(shù)生成的操作,這樣 orderby
得到的排序依據(jù)自然就是這些生成的隨機(jī)數(shù)值。我只要讓每一個(gè)元素都綁定上這些隨機(jī)數(shù)數(shù)值,然后讓隨機(jī)數(shù)數(shù)值進(jìn)行大小排序,是不是就意味著原來的序列也就排序了???
題目 6
利用好 group x by x
的語義就是這個(gè)題目破題的關(guān)鍵。我們知道 group
和 by
是用來分組的,那么如果拿它自己來分組是啥意思呢?是不是就是找到和自己相同的單詞,然后構(gòu)成一組?那么整個(gè)序列就可以通過這樣的分組機(jī)制得到一組一組的單詞序列,每一組內(nèi)的單詞是相同的,而組和組之間是不同的單詞。那么只要判斷每一組到底是不是只有一個(gè)元素,就可以知道它是不是只出現(xiàn)一次了。
題目 7
這個(gè)題目的 word == word.ToUpper()
不好想到。
題目 8
轉(zhuǎn)置就是行列交換,因此落實(shí)到每一個(gè)元素的話,自然就是 a[x][y]
到 a[y][x]
的操作。
屬于是套娃用法了。注意這里的套娃是寫在 select
后面的。這個(gè)用法出現(xiàn)極其罕見,不過這里確實(shí)有幫助。本題需要轉(zhuǎn)置整個(gè)數(shù)組,由于鋸齒數(shù)組是數(shù)組的數(shù)組,所以我們可以試著遍歷數(shù)組,然后進(jìn)行逐個(gè)元素的轉(zhuǎn)換。
這里的 Enumerable.Range(0, matrix.Length)
等于是獲取整個(gè)鋸齒數(shù)組有多少個(gè)元素。注意,這個(gè)數(shù)組是鋸齒數(shù)組,所以要被看成是 數(shù)組的數(shù)組,因此整個(gè)序列只有 5 個(gè)元素——它是由 5 個(gè) int[]
類型的元素構(gòu)成的一個(gè)一維數(shù)組。那么,這個(gè)地方迭代的時(shí)候,相當(dāng)于是 ?new[] { 0, 1, 2, 3, 4 }
的意思,只不過寫成剛才那樣就會(huì)比較通用化一些。
接著,注意套娃用法。我們?cè)诶锩媸褂昧艘粋€(gè)查詢表達(dá)式,作為外層查詢表達(dá)式的映射表達(dá)式結(jié)果??聪聝?nèi)層這個(gè)查詢表達(dá)式,from eachRow in matrix
還比較好理解,因?yàn)?matrix
是數(shù)組的數(shù)組,所以它迭代出來的每一個(gè)變量自然就是 int[]
類型的。而序列是橫著寫的,所以我們可以認(rèn)為這個(gè)矩陣其實(shí)就是一行一行存儲(chǔ)的這么一個(gè)數(shù)據(jù)結(jié)構(gòu)。接著,eachRow[i]
是在獲取這個(gè)行里的指定位置上的元素。看清楚,這里我們用的是外層查詢表達(dá)式里的 i
變量。而一次外層的迭代,我們就可以獲取一整個(gè)查詢表達(dá)式的結(jié)果,這個(gè)才是這個(gè)完整查詢表達(dá)式的書寫邏輯。
可以看到,i
的每一次更新,都會(huì)引發(fā)一整個(gè)內(nèi)層查詢表達(dá)式 from eachRow in matrix select eachRow[i]
的表達(dá)式完整返回。i
是 0 的時(shí)候,我們可以得到 from eachRow in matrix select eachRow[0]
,是不是就是在獲取矩陣每一行的第 1 個(gè)元素?而再次更新 ?i
為 1 的時(shí)候,這個(gè)查詢表達(dá)式就變?yōu)榱双@取矩陣每一行的第 2 個(gè)元素?
這么算下來,我一次 i
的變化,都映射到“每一行的第 n 個(gè)元素”,這是不是就是在取出每一列的元素?所以,我們?cè)谡{(diào)用和使用這個(gè) selection
變量的時(shí)候,可以發(fā)現(xiàn),它需要兩層循環(huán)來迭代,大概是這樣的格式:
是的,這次我們需要兩層循環(huán)。外層循環(huán)是 selection
的每一個(gè)元素,對(duì)應(yīng)了矩陣的每一列的元素。然后內(nèi)層則表示遍歷的是每一個(gè) selection
元素(是一個(gè)序列)里的每一個(gè)元素。
這么一來,自然就是轉(zhuǎn)置成功了。