0%

題目:把英文字母轉成它的位置,忽略所有非英文字母

Example:

alphabet_position(“The sunset sets at twelve o’ clock.”)

Should return “20 8 5 19 21 14 19 5 20 19 5 20 19 1 20 20 23 5 12 22 5 15 3 12 15 3 11” (as a string)

解題方向:

  1. 大小寫會影響它的位置號碼,按照它要的結果,應該是用小寫去算,所以先全部換成小寫。

    1
    2
    3
    def alphabet_position(text)
    text.downcase
    end
  2. 把所有非英文字母,包含空白字元全部用gsub取代。

    這邊有參考stackoverflow關於如何使用正規表示法去刪除非字元:

    https://stackoverflow.com/questions/5424354/regex-to-remove-non-letters

    1
    2
    3
    4
    5
    def alphabet_position(text)
    text.downcase.gsub(/[^a-zA-Z]/, '')
    end

    => "thesunsetsetsattwelveoclock"
  3. 用bytes轉換成它的位置,但同時也會轉變成陣列

    https://ruby-doc.org/core-2.5.1/String.html#method-i-bytes

    1
    2
    3
    4
    5
    def alphabet_position(text)
    text.downcase.gsub(/[^a-zA-Z]/, '').bytes
    end

    =>[116, 104, 101, 115, 117, 110, 115, 101, 116, 115, 101, 116, 115, 97, 116, 116, 119, 101, 108, 118, 101, 111, 99, 108, 111, 99, 107]
  4. 個別減96計算出正確位置

    1
    2
    3
    4
    5
    def alphabet_position(text)
    text.downcase.gsub(/[^a-zA-Z]/, '').bytes.map{ |i| i - 96 }
    end

    => [20, 8, 5, 19, 21, 14, 19, 5, 20, 19, 5, 20, 19, 1, 20, 20, 23, 5, 12, 22, 5, 15, 3, 12, 15, 3, 11]
  5. 最後再轉回字串並且加上空格完成題目

    1
    2
    3
    4
    5
    def alphabet_position(text)
    text.downcase.gsub(/[^a-zA-Z]/, '').bytes.map{ |i| i - 96 }.join(' ')
    end

    => "20 8 5 19 21 14 19 5 20 19 5 20 19 1 20 20 23 5 12 22 5 15 3 12 15 3 11"

新增、修改並儲存giveback資料

本篇內容是基於專案進行過程的紀錄,有一些前置內容可參考這篇:
如何在一個form裡面寫入兩個model的資料?(nested_form)

  1. 為避免畫面太過雜亂,我先將project的_form.html.erb裡面原本的giveback form內容拉出來render

    1
    2
    3
    4
    #views/project/_form.html.erb
    <%= form.fields_for :givebacks do |giveback| %>
    <%= render 'giveback_field', form: giveback %>
    <% end %>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #views/project/_giveback_field.html.erb
    <div class='m-3'>
    <%= form.label :title, '回饋商品' %>
    <%= form.text_field :title, class:'border-2'%>
    </div>
    <div class='m-3'>
    <%= form.label :price, '價格' %>
    <%= form.text_field :price, class:'border-2' %>
    </div>
  2. 修改_giveback_field.html.erb,建立一個div把form包起來,新增一個class以便後續js的操作,這邊選擇用content_tag是因為後面還要在裡面寫判斷。

    https://apidock.com/rails/ActionView/Helpers/TagHelper/content_tag

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #views/project/_giveback_field.html.erb
    <%= content_tag :div, class: "nested_field" do %>
    <div class='m-3'>
    <%= form.label :title, '回饋商品' %>
    <%= form.text_field :title, class:'border-2'%>
    </div>
    <div class='m-3'>
    <%= form.label :price, '價格' %>
    <%= form.text_field :price, class:'border-2' %>
    </div>
    <% end %>
  3. 在project的_form.html.erb裡面建立一個template標籤的giveback空表格,之後可以用stimulus js去抓它,塞進頁面裡面達到新增giveback表單的目的。

    1
    2
    3
    4
    5
    6
    #views/project/_form.html.erb
    <template>
    <%= form.fields_for :givebacks, Giveback.new, child_index: 'new_record' do |giveback| %>
    <%= render 'giveback_field', form: giveback %>
    <% end %>
    </template>
    • 這邊會選用template的標籤,是因為它在網頁載入時不會被渲染,但可以使用js把它實體化。

    • child_index:讓這個template標籤裡面的資料有不一樣的id跟name來區別它跟其他已產生的giveback form,後面我們會再用js去改變這個值,讓它是獨一無二的,可作為它的id,讓rails知道他是新的資料然後寫進資料庫,而其他的已經存在的物件,我們可以在資料庫找到它,所以只需要更新資料就可以了。
      Setting child_index while using nested attributes mass assignment

  4. 接下來就可以用一個div把這些fields_for的表單包進去,然後加上data-controller,交給stimulus來監管

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #views/project/_form.html.erb
    <div data-controller='nested-form' >

    <template data-nested-form-target='template'>
    <%= form.fields_for :givebacks, Giveback.new, child_index: 'new_record' do |giveback| %>
    <%= render 'giveback_field', form: giveback %>
    <% end %>
    </template>

    <%= form.fields_for :givebacks do |giveback| %>
    <%= render 'giveback_field', form: giveback %>
    <% end %>

    </div>
  5. 設定完controller,接下來就是我們要操控的target,既然要動態新增回饋商品,就需要一個按鈕來做這件事,另外最後再給它一個action,監聽它的點擊事件,然後去找相對應的controller及action。

    1
    2
    3
    4
    #views/project/_form.html.erb
    <div class='mb-3' data-nested-form-target='links'>
    <%= link_to '新增回饋商品', '#', data: { action: 'click->nested-form#add_new_giveback' } %>
    </div>
  6. 接下來我們就正式要開始進入stimulus了,首先在javascript資料夾裡面建立nested_form_controller.js(注意controller名字的慣例, 在html上是寫nested-form),然後寫下要控制的target

    1
    2
    3
    4
    #javascript/controllers/nested_form_controller.js
    export default class extends Controller {
    static targets = [ "links", "template" ]
    }
  7. 加上監聽click事件之後執行的方法add_new_giveback,先停止它的預設事件(點擊後跳頁),接著抓到templateTarget(命名慣例),把我們之前塞進去的child_index改掉,使用當下的時間替代掉new_record(不一定要取這個名字,可以自己任意命名),如此一來,每個點擊後加進來的資料的name跟id都不會重複,rails可以以此辨別是否為新的資料。

    1
    2
    3
    4
    5
    add_new_giveback(event) {
    event.preventDefault()

    let content = this.templateTarget.innerHTML.replace(/new_record/g, new Date().getTime())
    }

  8. 接著把這個新的表單塞到按鈕的上方。

    1
    2
    3
    4
    5
    6
    add_new_giveback(event) {
    event.preventDefault()

    let content = this.templateTarget.innerHTML.replace(/new_record/g, new Date().getTime())
    this.linksTarget.insertAdjacentHTML('beforebegin', content)
    }

    到這邊已經可以新增、修改並儲存giveback的資料了,接下來就是刪除的部分。

刪除giveback資料

跟新增修改時一樣,在頁面上的giveback資料有分成已經在資料庫的,以及還未進資料庫的,我們就從後者開始處理。

  1. 首先一樣先寫一個刪除資料的按鈕,然後監聽點擊事件,然後在後面外層的div下判斷,看這些資料是否還沒被儲存,如果是就回傳true。
    https://apidock.com/rails/ActiveRecord/Base/new_record%3F

    1
    2
    3
    4
    <%= content_tag :div, class: "nested_field", data: { new_record: form.object.new_record? } do %>
    ····(略)····
    <%= link_to '刪除回饋商品', '#', class:'mb-3 text-red-400', data: { action: 'click->nested-form#remove_giveback'}%>
    <% end %>

  2. 回到js這邊寫方法,一樣先停止預設行為,然後找到表單最外層的div,如果判斷它還未被寫進資料庫,點擊刪除鍵之後直接整個remove掉。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    remove_giveback(event) {
    event.preventDefault()

    let wrapper = event.target.closest('.nested_field')
    if (wrapper.dataset.newRecord == 'true'){
    wrapper.remove()
    }else{
    }
    }
  3. 接著處理以及存在的資料,還記得我們在上一篇原本是使用checkbox來對_destoy這個屬性給true或false嗎?如果是true,當我們按下送出表單後,它就會被從資料庫移除,用js寫的概念也一樣,我們先在後面給他一個_destroy的隱藏欄位。

    1
    2
    <%= link_to '刪除回饋商品', '#', class:'mb-3 text-red-400', data: { action: 'click->nested-form#remove_giveback'}%>
    <%= form.hidden_field :_destroy %>
  4. 在js這邊我們先選到那個隱藏欄位並把值改成true,然後先把它隱藏起來,當按下送出資料的按鈕時,才真的把它刪除。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    remove_giveback(event) {
    event.preventDefault()

    let wrapper = event.target.closest('.nested_field')
    if (wrapper.dataset.newRecord == 'true'){
    wrapper.remove()
    }else{
    wrapper.querySelector("input[name*='_destroy']").value = 'true'
    wrapper.style.display = 'none'
    }
    }

題目:找到陣列中只有一個的數字

Example:

1
2
[1, 1, 2] ==> 2
[17, 17, 3, 17, 17, 17, 17] ==> 3

解題方向:

看到要對陣列裡的元素做事情時,都會先想到each, map, select這幾個api,這邊可以用each搭配count去算每個元素的數量,如果等於1代表只有一個,就回傳這個值。

1
2
3
def stray (numbers)
numbers.each { |x| return x if numbers.count(x) == 1}
end

說明:將數字從大到小排列

題目:

1
2
3
Test.assert_equals(descending_order(0), 0)
Test.assert_equals(descending_order(1), 1)
Test.assert_equals(descending_order(123456789), 987654321)

初始狀態:

1
2
def descending_order(n)
end

解題方向:

  1. 因為題目裡面有兩個只有一位數,所以可以先下判斷,如果只有一位數,就回傳那個數字,這樣前兩個題目就過了。

    1
    2
    3
    4
    5
    def descending_order(n)
    if n < 10
    return n
    end
    end
  2. 如果要反轉數字的話,可以用digit來做,在不帶任何參數的情況下,可以將其反轉並形成一個陣列,再用sort排序,reverse反轉,後續join轉成字串,最後轉回數字

    1
    2
    3
    4
    5
    6
    7
    def descending_order(n)
    if n < 10
    return n
    else
    n.digits.sort.reversejoin.to_i
    end
    end

題目:取小於number的數字中,可以被3以及5整除的數字的總和,如果是3跟5的公倍數,則只取一次。

1
2
3
4
5
6
7
8
9
def test(actual, expected)
Test.assert_equals(actual, expected)
end

Test.describe("example tests") do
test(solution(10), 23)
test(solution(20), 78)
test(solution(200), 9168)
end

解題方向:

  1. 先把1-number的數字抓出來變成一個陣列

    (註:後來發現其實可以用1…number只取小於的數字,不包含最大值)

    1
    2
    3
    def solution(number)
    (1..number - 1)
    end
  2. 要對每個元素做選取的動作,所以可以用select,然後取可以整除3或5的數字出來

    1
    2
    3
    def solution(number)
    (1..number - 1).select { |x| x % 3 == 0 || x % 5 == 0 }
    end
  3. 最後再用sum相加得到最後結果

說明:這次沒有說明,就是按照以下邏輯去完成題目。

1
2
3
accum("abcd") -> "A-Bb-Ccc-Dddd"
accum("RqaEzty") -> "R-Qq-Aaa-Eeee-Zzzzz-Tttttt-Yyyyyyy"
accum("cwAt") -> "C-Ww-Aaa-Tttt"

題目:

1
2
3
4
5
6
7
8
9
Test.describe("accum") do
Test.it("Basic tests") do
Test.assert_equals(accum("ZpglnRxqenU"), "Z-Pp-Ggg-Llll-Nnnnn-Rrrrrr-Xxxxxxx-Qqqqqqqq-Eeeeeeeee-Nnnnnnnnnn-Uuuuuuuuuuu")
Test.assert_equals(accum("NyffsGeyylB"), "N-Yy-Fff-Ffff-Sssss-Gggggg-Eeeeeee-Yyyyyyyy-Yyyyyyyyy-Llllllllll-Bbbbbbbbbbb")
Test.assert_equals(accum("MjtkuBovqrU"), "M-Jj-Ttt-Kkkk-Uuuuu-Bbbbbb-Ooooooo-Vvvvvvvv-Qqqqqqqqq-Rrrrrrrrrr-Uuuuuuuuuuu")
Test.assert_equals(accum("EvidjUnokmM"), "E-Vv-Iii-Dddd-Jjjjj-Uuuuuu-Nnnnnnn-Oooooooo-Kkkkkkkkk-Mmmmmmmmmm-Mmmmmmmmmmm")
Test.assert_equals(accum("HbideVbxncC"), "H-Bb-Iii-Dddd-Eeeee-Vvvvvv-Bbbbbbb-Xxxxxxxx-Nnnnnnnnn-Cccccccccc-Ccccccccccc")
end
end

解題:

  1. 先將題目的字串全部統一換成小寫

    1
    2
    Expected: "Z-Pp-Ggg-Llll-Nnnnn-Rrrrrr-Xxxxxxx-Qqqqqqqq-Eeeeeeeee-Nnnnnnnnnn-Uuuuuuuuuuu", 
    instead got: "zpglnrxqenu"
  2. 用chars把每個字拆開,同時變成一個陣列

    1
    2
    Expected: "Z-Pp-Ggg-Llll-Nnnnn-Rrrrrr-Xxxxxxx-Qqqqqqqq-Eeeeeeeee-Nnnnnnnnnn-Uuuuuuuuuuu", 
    instead got: ["z", "p", "g", "l", "n", "r", "x", "q", "e", "n", "u"]
  3. 用map.with_index方法對陣列每一個字元做事情,這邊用with_index可以抓到每個字元的索引值,然後讓他們倍數複製

    1
    2
    Expected: "Z-Pp-Ggg-Llll-Nnnnn-Rrrrrr-Xxxxxxx-Qqqqqqqq-Eeeeeeeee-Nnnnnnnnnn-Uuuuuuuuuuu", 
    instead got: ["z", "pp", "ggg", "llll", "nnnnn", "rrrrrr", "xxxxxxx", "qqqqqqqq", "eeeeeeeee", "nnnnnnnnnn", "uuuuuuuuuuu"]
  4. 用capitalize讓每個元素的首字大寫

    1
    2
    Expected: "Z-Pp-Ggg-Llll-Nnnnn-Rrrrrr-Xxxxxxx-Qqqqqqqq-Eeeeeeeee-Nnnnnnnnnn-Uuuuuuuuuuu", 
    instead got: ["Z", "Pp", "Ggg", "Llll", "Nnnnn", "Rrrrrr", "Xxxxxxx", "Qqqqqqqq", "Eeeeeeeee", "Nnnnnnnnnn", "Uuuuuuuuuuu"]
  5. 最後用join(‘-‘)把所有元素用’-‘符號穿在一起變成字串,得到最後結果

    1
    2
    3
    def accum(s)
    s.downcase.chars.map.with_index {|x, index| (x * (index + 1)).capitalize}.join('-')
    end

取一段字串中間的文字

1
2
3
4
5
6
7
Kata.getMiddle("test") should return "es"

Kata.getMiddle("testing") should return "t"

Kata.getMiddle("middle") should return "dd"

Kata.getMiddle("A") should return "A"

解題思考:從題目上看起來,要將每個字串分成單數或偶數,再針對個別去計算要取的index值

1.如果是單數,就將字串長度除以2再四捨五入,取到中間數

2.如果是偶數,將字串長度除以2再減掉1,加上字串長度除以2

3.雖然方法笨笨的,但有效XD

1
2
3
4
5
6
7
8
def get_middle(s)
arr = s.split('')
if arr.length.odd?
arr[((arr.length) / 2).round]
else
arr[(arr.length) / 2 - 1] + arr[(arr.length) / 2 ]
end
end

解題完之後看到很聰明的作法,一行就搞定!在這裡也跟大家分享~

1
2
3
def get_middle(s)
s[(s.size-1)/2..s.size/2]
end

題目說明:請把陣列裡面中偶數平方、單數做開根號,然後取最後的總和。

結果:

1
2
3
Test.assert_equals(sum_square_even_root_odd([4, 5, 7, 8, 1, 2, 3, 0]), 91.61)

Test.assert_equals(sum_square_even_root_odd([1, 14, 9, 8, 17, 21]), 272.71)

解題說明:

用map對陣列內每個元素做判斷,如果是基數,就乘以0.5次方;如果是偶數,就乘以2次方。最後sum加總再用round取小數點兩位

1
2
3
4
5
def sum_square_even_root_odd(nums)

nums.map{ |x| x.odd? ? x ** 0.5 : x ** 2 }.sum.round(2)

end

題目說明:取最大值與最小值

1
Test.assert_equals(high_and_low("4 5 29 54 4 0 -214 542 -64 1 -3 6 -6"), "542 -214")

起始內容:

1
2
def high_and_low(numbers)
end

解題步驟:

1.把字串裡的每個字用split分開,並且得到新的陣列

1
2
3
4
5
def high_and_low(numbers)
numbers.split
end

=>["4", "5", "29", "54", "4", "0", "-214", "542", "-64", "1", "-3", "6", "-6"]

2.用map對陣列每個元素做轉換成數字

1
2
3
4
5
def high_and_low(numbers)
numbers.split.map{|x| x.to_i}
end

=>[4, 5, 29, 54, 4, 0, -214, 542, -64, 1, -3, 6, -6]

3.用minmax取最小及最大值,再reverse把最大值放前面

1
2
3
4
5
def high_and_low(numbers)
numbers.split.map{|x| x.to_i}.minmax.reverse
end

=>[542, -214]

4.最後join把陣列變回字串,並且在中間加上空格

1
2
3
4
5
def high_and_low(numbers)
numbers.split.map{|x| x.to_i}.minmax.reverse.join(' ')
end

=>"542 -214"

在剛學習rails的時候,表單通常都是單純的一個model填完欄位就送出,直到實際專案在進行時,才遇到需要將兩個model放在同一個form裡面,同時可以儲存資料到個別model的資料庫的需求。這時候就需要用到本篇文章要介紹的主角:Nested form

在我們募資網站的專案例子中,project跟givebacks(回饋商品)是一對多的關係,而我們想在project new頁面的form裡面,同時放入giveback的欄位,可使用fields_for,將兩個model放在同一個form裡面。

1
2
3
4
5
6
7
8
9
10
#project.rb
class Project < ApplicationRecord
belongs_to :user
has_many :givebacks
end

#giveback.rb
class Giveback < ApplicationRecord
belongs_to :project
end

按照官方文件的說明,我大概將它分成四個步驟:

  1. 建立一個attribute writer方法到project model裡面

    1
    2
    3
    4
    5
    6
    #project.rb
    class Project < ApplicationRecord
    belongs_to :user
    has_many :givebacks
    accepts_nested_attributes_for :givebacks
    end
  2. 用fields_for方法建立nested form(請注意:fields_for後面接的是符號)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #_form.html.erb

    <%= form_for(project) do |form| %>
    <div class="project-title">
    <%= form.label :title, '專案標題' %>
    <%= form.text_field :title%>
    </div>

    ·········(略)···········
    #下面是giveback的欄位
    <div>
    <%= form.fields_for :givebacks do |givebacks_form| %>
    <%= givebacks_form.label :回饋商品名稱 %>
    <%= givebacks_form.text_field :title %>
    </div>
    <div>
    <%= givebacks_form.label :價格 %>
    <%= givebacks_form.text_field :price %>
    </div>

    <% end %>
    <%= form.submit %>
    <% end %>
  3. 將giveback的資料加到permit

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #projects_controller.rb
    def project_params
    params.require(:project).permit(
    :title,
    :summary,
    :content,
    :image,
    :target_amount,
    :user_id,
    :due_date,
    :status,
    :content_image,
    :category_id,
    givebacks_attributes: [:id, :title, :price, :deliver_time, :_destroy, :image])
    end

    註:id也要被permit過去,否則在update project資料時,giveback因為沒有id的關係,會被判斷成我們要create一個新的,一直製造新的giveback實體。

  4. 在project controller的new方法裡面建立givebacks的實體

    1
    2
    3
    4
    5
    #projects_controller.rb
    def new
    @project = Project.new
    @project.givebacks.build
    end

    這時候填完資料按下送出,應該就可以看到兩個model的資料同時寫入資料庫了。

備註:在第四個步驟中,我們只能建立一個giveback的實體,如果需要要建立多個實體及資料,可使用times的方法。

1
2
3
4
def new
@project = Project.new
5.times {@project.givebacks.build}
end

如何刪除fields_for的資料(後續更新用js來做)

  1. 在fields_for裡面設定刪除的checkbox選項

    1
    2
    3
    4
    <div>
    <%= givebacks_form.check_box :_destroy %>
    <%= givebacks_form.label :_destroy, '刪除回饋商品' %>
    </div>
  2. 在project的model裡面設定allow_destroy

    1
    2
    3
    4
    5
    class Project < ApplicationRecord
    belongs_to :user
    has_many :givebacks
    accepts_nested_attributes_for :givebacks, allow_destroy: true
    end

    這只是目前我理出的概略步驟,其他詳細說明及用法可參考官方文件:

https://guides.rubyonrails.org/form_helpers.html#building-complex-forms