Filter ง่ายๆ สไตล์ Kotlin

เร็วกว่ารอกาแฟดริป

Travis P
Black Lens
Published in
3 min readMay 23, 2017

--

การ filter ใน Java 6 ไม่ใช่เรื่องยากครับ เรามี list อยู่อันนึง อยากจะกรองด้วยเงื่อนไขบางอย่าง ก็แค่สร้าง list ใหม่ วน for-loop เช็ค if ถ้าเข้าเงื่อนไขก็เติมเข้า list ใหม่

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// numbers = [1,2,3,4,5,6]
List<Integer> even = new ArrayList<>();
for (Integer i : numbers) {
if (i % 2 == 0) {
even.add(i);
}
}
// even = [2,4,6]

โค้ดแค่ไม่กี่บรรทัดเอง ไม่ใช่เรื่องใหญ่อะไรนักหนา… จนกระทั่งคุณต้อง filter ทุกอย่างบนโลกนี้ ไม่ว่าคุณจะ filter อะไร ที่ไหน กี่ครั้ง คุณต้องสร้าง list อันใหม่ วน for-loop เช็คเงื่อนไงตลอด ทั้งๆที่สิ่งเดียวที่เปลี่ยนคือเงื่อนไขเท่านั้น!

ชีวิตผมเปลี่ยนเมื่อผมเขียน Kotlin ครับ การ filter ใน Kotlin ง่ายแสนง่าย ไม่ต้องมาวนฟงวนฟอเอง ใส่เงื่อนไขเดียวจบ!

การ filter ใน Koltin

val numbers = listOf(1,2,3,4,5,6)
// numbers = [1,2,3,4,5,6]
val even = numbers.filter { it % 2 == 0 }
// even = [2,4,6]
val odd = numbers.filter { it % 2 != 0 }
// odd = [1,3,5]
val strings = listOf("one","two","three","four")
// strings = ["one","two","three","four"]
val longerThanThree = strings.filter { it.length > 3 }
// longerThanThree = ["three","four"]

การ filter ใน Kotlin ง่ายมากครับ เพียงแค่คุณมี List คุณเรียก method filter คุณใส่เงื่อนไข เสร็จ! คุณได้ list ที่ filter แล้ว หนึ่งบรรทัดเท่านั้น แถมอ่านเข้าใจด้วย ไม่ว่าคุณจะ filter กี่ที่ ใส่แค่เงื่อนไขเท่านั้นครับ

filter ทำงานยังไง

เรามาดู source code ของ filter จาก kotlin-stdlib กันดีกว่า

ตรงนี้ต้องอาศัยความเข้าใจในเรื่อง Extension Function และ Functional Programming นิดนึงครับ (มีลิ้งค์อยู่ด้านล่าง)

filter เป็น higher order extension function ที่ประกาศบน Iterable (ซึ่ง List extends Collection แล้ว Collection extends Iterable มาอีกที)

filter รับ parameter 1 ตัวชื่อ predicate ประเภทฟังก์ชั่นรับ T คืน Boolean

filter เรียก filterTo พร้อมส่ง ArrayList ใหม่กับ predicate เดิมไปให้

filterTo เป็น extension function บน Iterable เหมือนกัน

filterTo วน for ใน this ซึ่ง this ในที่นี้คือ Iterable ตัวที่เราเรียก filter นั่นแหละ

ในแต่ละรอบของลูปทำการเช็คเงื่อนไขตาม predicate ที่เราส่งเข้ามา ถ้าตรงเงื่อนไขก็เติม element นั้นเข้าไปใน destination (ที่เมื่อกี้ filter เพิ่งส่ง ArrayList เข้ามา) จากนั้น return destination ออกไป

สรุปสั้นๆคือ filter ทำงานซ้ำซากให้เราหมดเลย ทั้ง new ArrayList ทั้งวน for ทั้ง เช็คเงื่อนไข ทั้งเติม element เข้าไปใน list ใหม่ เราเพียงส่งเงื่อนไขให้มันในรูปแบบตัวแปรฟังก์ชั่นเท่านั้นเอง

ทำ filter เองด้วย Java 6 ได้มั้ย

ได้ครับ แต่ Java 6 ไม่ support extension function ดังนั้นเราทำได้ 2 วิธี คือ
1 subclass ArrayList มาเติม filter
2 สร้างเป็น static utility method

เนื่องจาก Java 6 ไม่ support higer order function เช่นกัน เราจึงต้องจำลอง predicate: (T) -> Boolean กันก่อน

Predicate

Predicate เป็นแค่ interface ไว้ให้เราห่อเงื่อนไขการ filter ของเราเฉยๆ ซึ่งมันก็คือๆกันกับตัวแปรประเภทฟังก์ชั่นน่ะแหละ ห่อโค้ดเอาไว้ใช้

Subclass ArrayList

ทำออกมาคร่าวๆก็คงประมาณนี้ครับ ต่อไปนี้เราอยากใช้ List ที่ filter ได้ก็ต้องใช้ FilterableArrayList แทน

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6);
// ints = [1,2,3,4,5,6]
FilterableArrayList<Integer> numbers =
new FilterableArrayList(ints);
// numbers = [1,2,3,4,5,6]
List<Integer> even = numbers.filter(
new Predicate<Integer>() {
@Override
Boolean check(Integer element) {
return element % 2 == 0;
}
}
}
// even = [2,4,6]
List<Integer> odd = numbers.filter(
new Predicate<Integer>() {
@Override
Boolean check(Integer element) {
return element % 2 != 0;
}
}
}
// odd = [1,3,5]

คราวนี้จะ filter อะไร สิ่งเดียวที่เปลี่ยนคือเงื่อนไขเท่านั้น อาจจะอุบาทว์หน่อยตรงที่เราต้องสร้าง anonymous class ของ Predicate (ซึ่งแก้ได้ด้วยการใช้ retrolambda)

ถึงแม้จะสะดวก สามารถเรียก method filter บนตัวแปรได้เลย แต่ก็ไม่ยืดหยุ่น เพราะใช้ได้เฉพาะ FilterableArrayList เท่านั้น

Static Utility Method

สำหรับวิธีนี้ใช้ได้กับทุก List เลย แต่ก็จะลำบากหน่อยเพราะต้องเรียก static method จาก util class ไม่ได้เรียกจากตัวแปรตรงๆ

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// numbers = [1,2,3,4,5,6]
List<Integer> even = ListUtil.filter(numbers,
new Predicate<Integer>() {
@Override
Boolean check(Integer element) {
return element % 2 == 0;
}
}
}
// even = [2,4,6]
List<Integer> odd = ListUtil.filter(numbers,
new Predicate<Integer>() {
@Override
Boolean check(Integer element) {
return element % 2 != 0;
}
}
}
// odd = [1,3,5]

สรุป

filter ทำงานหนัก ซ้ำๆ ที่น่าเบื่อให้เรา ทั้งวน for ทั้ง if ทั้ง add โดยเราแค่ใส่เงื่อนไขเท่านั้น ซึ่งความง่ายนี้เกิดขึ้นได้เพราะ extension function และ higher order function ที่ Kotlin มี

หากพยายาม implement เองด้วย Java 6 ก็พอได้ แต่จะติดปัญหาที่ syntax anonymous class น่าเกลียด และต้องเรียกใช้จาก utility class

เห็นอย่างนี้แล้วมาร่วม #ทีมKotlin กันเถอะครับ

--

--

Travis P
Black Lens

Android Developer, Kotlin & Flutter Enthusiast and Gamer