Kotlin Koans Workshop - II. collections
이미지 출처: https://kotlinlang.org/assets/images/open-graph/kotlin_250x250.png
테스트케이스로 코틀린 문법익히기
Chapter2 - Kotlin Collection 표준라이브러리 이용하기
Kotlin Github repository의 kotlin-koans 프로젝트를 기반으로 포스팅 되었음을 밝힘니다.
출처: https://github.com/Kotlin/kotlin-koans
목차
- 코틀린 컬렉션 toSet
- 코틀린 컬렉션 map, filter
- 코틀린 컬렉션 any, all, count, firstOrNull
- 코틀린 컬렉션 flatMap
- 코틀린 컬렉션 maxBy
- 코틀린 컬렉션 sortBy
- 코틀린 컬렉션 sumByDouble
- 코틀린 컬렉션 groupBy
- 코틀린 컬렉션 partition
- 코틀린 컬렉션 fold
- 코틀린 컬렉션 2개이상의 함수를 연속으로 호출하기
- 코틀린 표준라이브러리를 이용하여 Java코드를 Kotlin코드로 변경하기
예제용 Shop클래스 목차 ⇈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package ii_collections
data class Shop(val name: String, val customers: List<Customer>)
data class Customer(val name: String, val city: City, val orders: List<Order>) {
override fun toString() = "$name from ${city.name}"
}
data class Order(val products: List<Product>, val isDelivered: Boolean)
data class Product(val name: String, val price: Double) {
override fun toString() = "'$name' for $price"
}
data class City(val name: String) {
override fun toString() = name
}
예제용 TestShop클래스 목차 ⇈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package ii_collections.data
import ii_collections.*
fun main(arge: Array<String>) {
val a: MutableList<Int>? = mutableListOf(1, 2, 3)
a?.run{ add(7)}
// println(a.fold(1, { price, it -> price * it}))
}
//products
val idea = Product("IntelliJ IDEA Ultimate", 199.0)
val reSharper = Product("ReSharper", 149.0)
val dotTrace = Product("DotTrace", 159.0)
val dotMemory = Product("DotTrace", 129.0)
val dotCover = Product("DotCover", 99.0)
val appCode = Product("AppCode", 99.0)
val phpStorm = Product("PhpStorm", 99.0)
val pyCharm = Product("PyCharm", 99.0)
val rubyMine = Product("RubyMine", 99.0)
val webStorm = Product("WebStorm", 49.0)
val teamCity = Product("TeamCity", 299.0)
val youTrack = Product("YouTrack", 500.0)
//customers
val lucas = "Lucas"
val cooper = "Cooper"
val nathan = "Nathan"
val reka = "Reka"
val bajram = "Bajram"
val asuka = "Asuka"
val riku = "Riku"
//cities
val Canberra = City("Canberra")
val Vancouver = City("Vancouver")
val Budapest = City("Budapest")
val Ankara = City("Ankara")
val Tokyo = City("Tokyo")
fun customer(name: String, city: City, vararg orders: Order) = Customer(name, city, orders.toList())
fun order(vararg products: Product, isDelivered: Boolean = true) = Order(products.toList(), isDelivered)
fun shop(name: String, vararg customers: Customer) = Shop(name, customers.toList())
val shop = shop("jb test shop",
customer(lucas, Canberra,
order(reSharper),
order(reSharper, dotMemory, dotTrace)
),
customer(cooper, Canberra),
customer(nathan, Vancouver,
order(rubyMine, webStorm)
),
customer(reka, Budapest,
order(idea, isDelivered = false),
order(idea, isDelivered = false),
order(idea)
),
customer(bajram, Ankara,
order(reSharper)
),
customer(asuka, Tokyo,
order(idea)
),
customer(riku, Tokyo,
order(phpStorm, phpStorm),
order(phpStorm)
)
)
val customers: Map<String, Customer> = shop.customers.fold(hashMapOf<String, Customer>(), {
map, customer ->
map[customer.name] = customer
map
})
val orderedProducts = setOf(idea, reSharper, dotTrace, dotMemory, rubyMine, webStorm, phpStorm)
val sortedCustomers = listOf(cooper, nathan, bajram, asuka, lucas, riku, reka).map { customers[it] }
val groupedByCities = mapOf(
Canberra to listOf(lucas, cooper),
Vancouver to listOf(nathan),
Budapest to listOf(reka),
Ankara to listOf(bajram),
Tokyo to listOf(asuka, riku)
).mapValues { it.value.map { name -> customers[name] } }
코틀린 컬렉션 toSet 목차 ⇈
src package
1
2
3
4
5
6
7
// Shop 클래스에 getSetOfCustomers() 함수를 정의합니다.
fun Shop.getSetOfCustomers(): Set<Customer> {
// Return a set containing all the customers of this shop
// toSet 함수는 컬렉션에 포함된 엘리먼트를 Set객체에 담아서 반환합니다.
// 모든 엘리먼트는 원본 Iteration의 순서를 유지합니다.
return this.customers.toSet()
}
test package
1
2
3
@Test fun testSetOfCustomers() {
assertEquals(customers.values.toSet(), shop.getSetOfCustomers())
}
코틀린 컬렉션 map, filter 목차 ⇈
src package
1
2
3
4
5
6
7
8
9
10
11
12
13
fun Shop.getCitiesCustomersAreFrom(): Set<City> {
// Return the set of cities the customers are from
// map 함수는 지정된 변환 함수를 원본 컬렉션의 각 요소에 적용하고 결과값을 반환합니다.
// customers.map { element -> element.city }
// element 네임을 지정하지 않아도 it 키워드로 element에 접근이 가능합니다.
return customers.map { it.city }.toSet()
}
fun Shop.getCustomersFrom(city: City): List<Customer> {
// Return a list of the customers who live in the given city
// filter 함수는 지정된 조건과 일치하는 요소의 리스트를 반환합니다.
return customers.filter { it.city == city }
}
test package
1
2
3
@Test fun testCustomersFromCity() {
assertEquals(listOf(customers[lucas], customers[cooper]), shop.getCustomersFrom(Canberra))
}
코틀린 컬렉션 any, all, count, firstOrNull 목차 ⇈
src package
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
fun Customer.isFrom(city: City): Boolean {
// Return true if the customer is from the given city
return this.city == city
}
fun Shop.checkAllCustomersAreFrom(city: City): Boolean {
// Return true if all customers are from the given city
// 컬렉션의 모든 element가 주어진 조건과 일치하면 true를 반환하고 그렇지 않으면 false를 반환합니다.
return customers.all { it.isFrom(city)}
}
fun Shop.hasCustomerFrom(city: City): Boolean {
// Return true if there is at least one customer from the given city
// 컬렉션의 element중 주어진 조건과 일치하는 element가 1개 이상이면 true를 반환하고 그렇지 않으면 false를 반환합니다.
return customers.any { it.isFrom(city) }
}
fun Shop.countCustomersFrom(city: City): Int {
// Return the number of customers from the given city
// 컬렉션의 element중 주어진 조건과 일치하는 element의 수를 반환합니다.
return customers.count { it.isFrom(city) }
}
fun Shop.findFirstCustomerFrom(city: City): Customer? {
// Return the first customer who lives in the given city, or null if there is none
// 컬렉션의 element중 주어진 조건과 일치하는 첫번째 element를 반환합니다.
// 일치하는 element가 없으면 null를 반환합니다.
return customers.firstOrNull { it.isFrom(city) }
}
test package
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test fun testCustomerIsFromCity() {
assertTrue(customers[lucas]!!.isFrom(Canberra))
assertFalse(customers[lucas]!!.isFrom(Budapest))
}
@Test fun testAllCustomersAreFromCity() {
assertFalse(shop.checkAllCustomersAreFrom(Canberra))
}
@Test fun testAnyCustomerIsFromCity() {
assertTrue(shop.hasCustomerFrom(Canberra))
}
@Test fun testCountCustomersFromCity() {
assertEquals(2, shop.countCustomersFrom(Canberra))
}
@Test fun testFirstCustomerFromCity() {
assertEquals(customers[lucas], shop.findFirstCustomerFrom(Canberra))
assertEquals(null, shop.findFirstCustomerFrom(City("Chicago")))
}
코틀린 컬렉션 flatMap 목차 ⇈
src package
1
2
3
4
5
6
7
8
9
10
11
// flatMap 함수는 원본 콜렉션의 각 요소에서 transform function이 실행된 결과로 얻은 모든 요소의 단일 목록을 리턴합니다.
val Customer.orderedProducts: Set<Product> get() {
// Return all products this customer has ordered
// 아래와 같이 flatMap을 이용하면 List<Order> 객체를 순회하며 Order.products를 모두 포함한 단일컬렉션을 반환할 수 있습니다.
return this.orders.flatMap { it.products }.toSet()
}
val Shop.allOrderedProducts: Set<Product> get() {
// Return all products that were ordered by at least one customer
return customers.flatMap { it.orderedProducts }.toSet()
}
test package
1
2
3
4
5
6
7
@Test fun testGetOrderedProductsSet() {
assertEquals(setOf(idea), customers[reka]!!.orderedProducts)
}
@Test fun testGetAllOrderedProducts() {
assertEquals(orderedProducts, shop.allOrderedProducts)
}
코틀린 컬렉션 maxBy 목차 ⇈
src package
1
2
3
4
5
6
7
8
9
10
11
fun Shop.getCustomerWithMaximumNumberOfOrders(): Customer? {
// Return a customer whose order count is the highest among all customers
// maxBy 함수는 주어진 조건의 값중 가장 큰값을 가지고 있는 element를 반환합니다.
// 만약 일치하는 값이 없으면 null값을 반환합니다.
return customers.maxBy { it.orders.size }
}
fun Customer.getMostExpensiveOrderedProduct(): Product? {
// Return the most expensive product which has been ordered
return orderedProducts.maxBy { it.price }
}
test package
1
2
3
4
5
6
7
@Test fun testCustomerWithMaximumNumberOfOrders() {
assertEquals(customers[reka], shop.getCustomerWithMaximumNumberOfOrders())
}
@Test fun testTheMostExpensiveOrderedProduct() {
assertEquals(rubyMine, customers[nathan]!!.getMostExpensiveOrderedProduct())
}
코틀린 컬렉션 sortBy 목차 ⇈
src package
1
2
3
4
5
fun Shop.getCustomersSortedByNumberOfOrders(): List<Customer> {
// Return a list of customers, sorted by the ascending number of orders they made
// sortedBy 함수는 주어진 조건을 기준으로 element를 정렬한 결과값을 반환합니다.
return customers.sortedBy { it.orders.size }
}
test package
1
2
3
@Test fun testGetCustomersSortedByNumberOfOrders() {
assertEquals(sortedCustomers, shop.getCustomersSortedByNumberOfOrders())
}
코틀린 컬렉션 sumByDouble 목차 ⇈
src package
1
2
3
4
5
6
fun Customer.getTotalOrderPrice(): Double {
// Return the sum of prices of all products that a customer has ordered.
// Note: a customer may order the same product for several times.
// sumByDouble 함수는 배열을 순회하며 선언된 속성값을 모두 합산하고 그 결과값을 반환합니다.
return orders.flatMap { it.products }.sumByDouble { it.price }
}
test package
1
2
3
4
5
6
7
@Test fun testGetTotalOrderPrice() {
assertEquals(148.0, customers[nathan]!!.getTotalOrderPrice(), 0.001)
}
@Test fun testTotalPriceForRepeatedProducts() {
assertEquals(586.0, customers[lucas]!!.getTotalOrderPrice(), 0.001)
}
코틀린 컬렉션 groupBy 목차 ⇈
src package
1
2
3
4
fun Shop.groupCustomersByCity(): Map<City, List<Customer>> {
// groupBy 함수는 배열 element의 키값(toString())을 기준으로 목록을 그룹화 하여 Map 객체를 반환합니다.
return customers.groupBy { it.city }
}
test package
1
2
3
@Test fun testGroupCustomersByCity() {
assertEquals(groupedByCities, shop.groupCustomersByCity())
}
코틀린 컬렉션 partition 목차 ⇈
src package
1
2
3
4
5
6
7
8
fun Shop.getCustomersWithMoreUndeliveredOrdersThanDelivered(): Set<Customer> {
// Return customers who have more undelivered orders than delivered
return customers.filter {
// partition 함수는 주어진 조건을 기준으로 true값은 첫번째 목록에, false값은 두번쨰 목록에 반환합니다.
val (delivered, undelivered) = it.orders.partition { it.isDelivered }
undelivered.size > delivered.size
}.toSet()
}
test package
1
2
3
@Test fun testGetCustomersWhoHaveMoreUndeliveredOrdersThanDelivered() {
assertEquals(setOf(customers[reka]), shop.getCustomersWithMoreUndeliveredOrdersThanDelivered())
}
코틀린 컬렉션 fold 목차 ⇈
src package
1
2
3
4
5
6
7
fun Shop.getSetOfProductsOrderedByEveryCustomer(): Set<Product> {
// Return the set of products ordered by every customer
return customers.fold(allOrderedProducts, {
orderedByAll, customer ->
orderedByAll.intersect(customer.orderedProducts)
})
}
test package
1
2
3
4
5
6
7
8
9
10
11
12
13
@Test fun testGetProductsOrderedByAllCustomers() {
val testShop = shop("test shop for 'fold'",
customer(lucas, Canberra,
order(idea),
order(webStorm)
),
customer(reka, Budapest,
order(idea),
order(youTrack)
)
)
assertEquals(setOf(idea), testShop.getSetOfProductsOrderedByEveryCustomer())
}
코틀린 컬렉션 2개이상의 함수를 연속으로 호출하기 목차 ⇈
src package
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun Shop.getCustomersWhoOrderedProduct(product: Product): Set<Customer> {
// Return the set of customers who ordered the specified product
return customers.filter { it.orderedProducts.contains(product) }.toSet()
}
fun Customer.getMostExpensiveDeliveredProduct(): Product? {
// Return the most expensive product among all delivered products
// (use the Order.isDelivered flag)
return orders.filter { it.isDelivered }.flatMap { it.products }.maxBy { it.price }
}
fun Shop.getNumberOfTimesProductWasOrdered(product: Product): Int {
// Return the number of times the given product was ordered.
// Note: a customer may order the same product for several times.
return customers.flatMap { it.orders }.flatMap { it.products }.count {it == product}
}
test package
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Test fun testGetCustomersWhoOrderedProduct() {
assertEquals(setOf(customers[reka], customers[asuka]), shop.getCustomersWhoOrderedProduct(idea))
}
@Test fun testMostExpensiveDeliveredProduct() {
val testShop = shop("test shop for 'most expensive delivered product'",
customer(lucas, Canberra,
order(isDelivered = false, products = idea),
order(reSharper)
)
)
assertEquals(reSharper, testShop.customers[0].getMostExpensiveDeliveredProduct())
}
@Test fun testNumberOfTimesEachProductWasOrdered() {
assertEquals(4, shop.getNumberOfTimesProductWasOrdered(idea))
}
@Test fun testNumberOfTimesEachProductWasOrderedForRepeatedProduct() {
assertEquals("A customer may order a product for several times",
3, shop.getNumberOfTimesProductWasOrdered(reSharper))
}
@Test fun testNumberOfTimesEachProductWasOrderedForRepeatedInOrderProduct() {
assertEquals("An order may contain a particular product more than once",
3, shop.getNumberOfTimesProductWasOrdered(phpStorm))
}
코틀린 표준라이브러리를 이용하여 Java코드를 Kotlin코드로 변경하기 목차 ⇈
src package(Java)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public Collection<String> doSomethingStrangeWithCollection(Collection<String> collection) {
Map<Integer, List<String>> groupsByLength = Maps.newHashMap();
for (String s : collection) {
List<String> strings = groupsByLength.get(s.length());
if (strings == null) {
strings = Lists.newArrayList();
groupsByLength.put(s.length(), strings);
}
strings.add(s);
}
int maximumSizeOfGroup = 0;
for (List<String> group : groupsByLength.values()) {
if (group.size() > maximumSizeOfGroup) {
maximumSizeOfGroup = group.size();
}
}
for (List<String> group : groupsByLength.values()) {
if (group.size() == maximumSizeOfGroup) {
return group;
}
}
return null;
}
src package(Kotlin)
1
2
3
4
5
fun doSomethingStrangeWithCollection(collection: Collection<String>): Collection<String>? {
val groupsByLength = collection.groupBy { s -> s.length }
return groupsByLength.values.maxBy { group -> group.size }
}
test package
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test fun testCollectionOfOneElement() {
doTest(listOf("a"), listOf("a"))
}
@Test fun testEmptyCollection() {
doTest(null, listOf())
}
@Test fun testSimpleCollection() {
doTest(listOf("a", "c"), listOf("a", "bb", "c"))
}
@Test fun testCollectionWithEmptyStrings() {
doTest(listOf("", "", "", ""), listOf("", "", "", "", "a", "bb", "ccc", "dddd"))
}
@Test fun testCollectionWithTwoGroupsOfMaximalSize() {
doTest(listOf("a", "c"), listOf("a", "bb", "c", "dd"))
}
private fun doTest(expected: Collection<String>?, argument: Collection<String>) {
assertEquals("The function 'doSomethingStrangeWithCollection' should do at least something with a collection:", expected, doSomethingStrangeWithCollection(argument))
}