-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresult.txt
More file actions
65 lines (43 loc) · 4.79 KB
/
result.txt
File metadata and controls
65 lines (43 loc) · 4.79 KB
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
𝗖𝗿𝗲𝗮𝘁𝗶𝗻𝗴 𝗮 𝗖𝘂𝘀𝘁𝗼𝗺 𝗞𝗼𝘁𝗹𝗶𝗻 𝗦𝗲𝗾𝘂𝗲𝗻𝗰𝗲 𝗢𝗽𝗲𝗿𝗮𝘁𝗼𝗿
Many of us use sequences in our projects and work with a variety of operators, but do you know how they work under the hood? Let’s create a custom operator that filters only odd-indexed elements and explore how to implement it, following examples from the Kotlin Sequence API.
𝗛𝗼𝘄 𝗦𝗲𝗾𝘂𝗲𝗻𝗰𝗲𝘀 𝗪𝗼𝗿𝗸 𝗶𝗻 𝗞𝗼𝘁𝗹𝗶𝗻
Each 𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦 in Kotlin is an interface that exposes an 𝘐𝘵𝘦𝘳𝘢𝘵𝘰𝘳:
𝘱𝘶𝘣𝘭𝘪𝘤 𝘪𝘯𝘵𝘦𝘳𝘧𝘢𝘤𝘦 𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦<𝘰𝘶𝘵 𝘛> {
𝘱𝘶𝘣𝘭𝘪𝘤 𝘰𝘱𝘦𝘳𝘢𝘵𝘰𝘳 𝘧𝘶𝘯 𝘪𝘵𝘦𝘳𝘢𝘵𝘰𝘳(): 𝘐𝘵𝘦𝘳𝘢𝘵𝘰𝘳<𝘛>
}
To implement a custom Sequence operator, we can create a class that implements this interface. All we need is to iterate through the elements, apply transformations, and return a resulting sequence.
𝗜𝗺𝗽𝗹𝗲𝗺𝗲𝗻𝘁𝗮𝘁𝗶𝗼𝗻
Here’s our class:
𝘤𝘭𝘢𝘴𝘴 𝘖𝘥𝘥𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦<𝘛>(𝘱𝘳𝘪𝘷𝘢𝘵𝘦 𝘷𝘢𝘭 𝘴𝘰𝘶𝘳𝘤𝘦: 𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦<𝘛>) : 𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦<𝘛> {
𝘰𝘷𝘦𝘳𝘳𝘪𝘥𝘦 𝘧𝘶𝘯 𝘪𝘵𝘦𝘳𝘢𝘵𝘰𝘳(): 𝘐𝘵𝘦𝘳𝘢𝘵𝘰𝘳<𝘛> = 𝘖𝘥𝘥𝘗𝘰𝘴𝘪𝘵𝘪𝘰𝘯𝘐𝘵𝘦𝘳𝘢𝘵𝘰𝘳(𝘴𝘰𝘶𝘳𝘤𝘦)
}
This class uses a form of the 𝗱𝗲𝗰𝗼𝗿𝗮𝘁𝗼𝗿 𝗽𝗮𝘁𝘁𝗲𝗿𝗻 — we decorate the iteration process of the upstream sequence. The 𝘖𝘥𝘥𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦 class takes the upstream sequence, iterates over it, selects elements at odd positions, and passes them to its iterator.
Now, we need a specialized iterator to check whether an element’s index is odd or even. To simplify the implementation, we use 𝘈𝘣𝘴𝘵𝘳𝘢𝘤𝘵𝘐𝘵𝘦𝘳𝘢𝘵𝘰𝘳, which is a utility class that helps calculate the next element lazily when 𝘯𝘦𝘹𝘵() or 𝘩𝘢𝘴𝘕𝘦𝘹𝘵() is called.
Here’s what the iterator looks like. It iterates through the upstream sequence, finds the next odd-positioned element, and adds it using 𝘴𝘦𝘵𝘕𝘦𝘹𝘵(). When no elements remain, we call 𝘥𝘰𝘯𝘦().
𝘪𝘯𝘵𝘦𝘳𝘯𝘢𝘭 𝘤𝘭𝘢𝘴𝘴 𝘖𝘥𝘥𝘗𝘰𝘴𝘪𝘵𝘪𝘰𝘯𝘐𝘵𝘦𝘳𝘢𝘵𝘰𝘳<𝘛>(𝘴𝘰𝘶𝘳𝘤𝘦: 𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦<𝘛>) : 𝘈𝘣𝘴𝘵𝘳𝘢𝘤𝘵𝘐𝘵𝘦𝘳𝘢𝘵𝘰𝘳<𝘛>() {
𝘱𝘳𝘪𝘷𝘢𝘵𝘦 𝘷𝘢𝘳 𝘪𝘯𝘥𝘦𝘹 = 0
𝘱𝘳𝘪𝘷𝘢𝘵𝘦 𝘷𝘢𝘭 𝘪𝘵𝘦𝘳𝘢𝘵𝘰𝘳 = 𝘴𝘰𝘶𝘳𝘤𝘦.𝘪𝘵𝘦𝘳𝘢𝘵𝘰𝘳()
𝘰𝘷𝘦𝘳𝘳𝘪𝘥𝘦 𝘧𝘶𝘯 𝘤𝘰𝘮𝘱𝘶𝘵𝘦𝘕𝘦𝘹𝘵() {
𝘸𝘩𝘪𝘭𝘦 (𝘪𝘵𝘦𝘳𝘢𝘵𝘰𝘳.𝘩𝘢𝘴𝘕𝘦𝘹𝘵()) {
𝘪𝘯𝘥𝘦𝘹++
𝘷𝘢𝘭 𝘯𝘦𝘹𝘵 = 𝘪𝘵𝘦𝘳𝘢𝘵𝘰𝘳.𝘯𝘦𝘹𝘵()
𝘪𝘧 (𝘪𝘴𝘌𝘭𝘪𝘨𝘪𝘣𝘭𝘦()) {
𝘴𝘦𝘵𝘕𝘦𝘹𝘵(𝘯𝘦𝘹𝘵)
𝘳𝘦𝘵𝘶𝘳𝘯
}
}
𝘥𝘰𝘯𝘦()
}
𝘱𝘳𝘪𝘷𝘢𝘵𝘦 𝘧𝘶𝘯 𝘪𝘴𝘌𝘭𝘪𝘨𝘪𝘣𝘭𝘦() = 𝘪𝘯𝘥𝘦𝘹 % 2 != 0
}
At this point, you might be wondering, “How do I use this in practice?” It’s simple:
𝘧𝘶𝘯 <𝘛> 𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦<𝘛>.𝘵𝘢𝘬𝘦𝘖𝘥𝘥𝘗𝘰𝘴𝘪𝘵𝘪𝘰𝘯𝘴() = 𝘖𝘥𝘥𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦(𝘵𝘩𝘪𝘴)
𝘧𝘶𝘯 𝘮𝘢𝘪𝘯() {
(1..100).𝘢𝘴𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦()
.𝘵𝘢𝘬𝘦𝘖𝘥𝘥𝘗𝘰𝘴𝘪𝘵𝘪𝘰𝘯𝘴()
.𝘰𝘯𝘌𝘢𝘤𝘩 { 𝘱𝘳𝘪𝘯𝘵𝘭𝘯("𝘍𝘪𝘭𝘵𝘦𝘳𝘦𝘥 𝘦𝘭𝘦𝘮𝘦𝘯𝘵: $𝘪𝘵") }
.𝘵𝘰𝘓𝘪𝘴𝘵()
}
You just create an extension function on 𝘚𝘦𝘲𝘶𝘦𝘯𝘤𝘦 to apply the operator and you’re ready to go.
𝗖𝗼𝗻𝗰𝗹𝘂𝘀𝗶𝗼𝗻
By exploring the implementation of operators you use in your code, you'll gain a deeper understanding of how they work. You'll often encounter similar patterns involving iterators, extension functions, and lazy computation. Some iterators may include predicates, transformation functions, or parameters for dropping or taking elements, but the fundamental concepts remain the same.