2018年振り返り
このエントリは今年一年の自己の振り返り Advent Calendar 2018 - Adventar25日目の記事です。
早いもので今年も終わりますね。ということでエンジニア3年目の一年の振り返りです。
仕事
転職した
2月にいわゆるスタートアップに転職しました。100人以上規模から10人規模への転職だったので環境が結構変わりましたが以外にもすんなりといけた気がしています。 技術スタック的にはiOS(Swift), Android(Kotlin, Java), RoR(Ruby)を触っているのですが来年もこの辺りで挑戦していくことにになりそう?です。 スタートアップなのでスピードは大事ですがテストを書く文化があるのでその辺りはしっかりやっていると思います。
プライベート
引っ越した
転職にあたり神奈川から東京に引っ越しました。良くも悪くも静かな街なので個人的には気に入ってます。
アラサー突入
ついにアラサーになりました。24才から社会人やってるので3年だけなのにもうアラサーなんですね。怖い。 若手から色々教えてもらってやっていこうと思います。
卓球を再開する
昔部活でやっていた卓球を再開しました。できるだけ週一でやるようにしていて社会人になってから初の大会出場を果たしました。
富士登山
夏頃に富士登山いったのですが悪天候のため8合目で下山になりました。 来年はリベンジして頂点取ります。
個人開発の停滞
個人開発をあまりやっていなかったのでこれは反省です。 iOS,Android共にストアから消え去っているのでこれは怠慢としか言いようがないです。
来年の抱負
Swiftで関数型データ構造
Swiftで関数型のデータ構造を作ってみます。
関数型データの構造は永続的であるという特徴があります。なので更新する際には既存のデータを破壊するのではなく新しいオブジェクトを作ります。Enum
を使ってこのリストを作ってみます。
cons
はリストの先頭、nil
はリストの終端を表します。
enum List<T: Equatable> { case `nil` indirect case cons(T, List<T>) }
またリストは比較できると便利なのでEquatable
を満たすようにします。
extension List: Equatable { static func ==(lhs: List, rhs: List) -> Bool { switch (lhs, rhs) { case (.`nil`, .`nil`): return true case (.cons(let h, let t), .cons(let h1, let t1)): return h == h1 && t == t1 default: return false } } } let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`))) let list1 = List<Int>.cons(1, .cons(2, .cons(3, .`nil`))) let list2 = List<Int>.cons(1, .cons(2, .cons(3, .cons(4, .`nil`)))) list == list1 // true list == list2 // false
CustomStringConvertible
を満たすようにすると出力が見やすくなるのでやっておきましょう
extension List: CustomStringConvertible { var description: String { switch self { case .`nil`: return "nil" case .cons(let head, let tail): return "\(head),\(tail)" } } } let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`))) print(list) //1,2,3,nil
リストなら以下のような要素の数を取得できるようにしたいですね。やってみましょう。
extension List { var isEmpty: Bool { switch self { case .`nil`: return true case .cons: return false } } var head: T? { switch self { case .`nil`: return nil case .cons(let head, _): return head } } var tail: List<T>? { switch self { case .`nil`: return nil case .cons(_, let tail): return tail } } var length: Int { switch self { case .`nil`: return 0 case .cons(_, let tail): return 1 + tail.length } } var last: T? { switch self { case .`nil`: return nil case .cons(let head, .`nil`): return head case .cons(_, let tail): return tail.last } } } let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`))) list.isEmpty // false list.head // 1 list.tail // 2,3,nil list.length // 3 list.last // 3
要素の追加、削除はできて当然ですね。これも実装していきます。 だいたいやっている事は同じです。リストの要素を辿っていき該当の要素に対して処理を行っています。
extension List { func add(value: T) -> List<T> { switch self { case .`nil`: return List<T>.cons(value, .`nil`) case .cons(let head, let tail): return .cons(head, tail.add(value: value)) } } func remove(index: Int) -> List<T> { switch (self, index) { case (.`nil`, _): return self case (.cons(_, let tail), 0): return tail case (.cons(let head, let tail), let index): return .cons(head, tail.remove(index: index - 1)) } } func update(index: Int, value: T) -> List<T> { switch (self, index) { case (.`nil`, _): return self case (.cons(_, let tail), 0): return .cons(value, tail) case (.cons(let head, let tail), let index): return .cons(head, tail.update(index: index - 1, value: value)) } } func append(list: List<T>) -> List<T> { switch self { case .`nil`: return list case .cons(let head, let tail): return List.cons(head, tail.append(list: list)) } } } let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`))) list.add(value: 100) // 1,2,3,100,nil list.remove(index: 1) // 1,3,nil list.update(index: 0, value: 100) // 100,2,3,nil list.append(list: list) // 1,2,3,1,2,3,nil
最後に高階関数を実装します。
extension List { func foldr<R>(acc: R, f: (T,R) -> R) -> R { switch self { case .`nil`: return acc case .cons(let head, let tail): return f(head, tail.foldr(acc: acc, f: f)) } } func foldl<R>(acc: R, f: (R,T) -> R) -> R { switch self { case .`nil`: return acc case .cons(let head, let tail): return tail.foldl(acc: f(acc, head), f: f) } } func map<R>(f: @escaping (T) -> R) -> List<R> { return foldr(acc: List<R>.`nil`) { acc, list in return List<R>.cons(f(acc), list) } } func filter(f: @escaping (T) -> Bool) -> List<T> { return foldr(acc: List<T>.`nil`, f: { acc, list in if f(acc) { return List<T>.cons(acc, list) } return list }) } func flatMap<R>(f: @escaping (T) -> List<R>) -> List<R> { return foldr(acc: List<R>.`nil`, f: { acc, list in return f(acc).append(list: list) }) } } let list = List<Int>.cons(1, .cons(2, .cons(3, .`nil`))) list.map { $0 * 10 } // "10,20,30,nil list.filter { $0 % 2 == 0 } // 2,nil list.flatMap { .cons($0, .cons($0, .`nil`)) } // 1,1,2,2,3,3,nil
特定文字をリンクするTextView
特定の文字をリンクにしたいときに使うTextView
LinkableTextView.kt
class LinkableTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : TextView(context, attrs) { private var target = "" private var color = 0 private var linkBold = false var listener: Listener? = null init { val a = context.theme.obtainStyledAttributes( attrs, R.styleable.LinkableTextView, 0, 0) try { target = a.getString(R.styleable.LinkableTextView_target) color = a.getColor(R.styleable.LinkableTextView_link_color, 0) linkBold = a.getBoolean(R.styleable.LinkableTextView_link_bold, false) } finally { a.recycle() } } override fun setText(text: CharSequence?, type: BufferType?) { if (TextUtils.isEmpty(target)) { super.setText(text, type) return } val p1 = Pattern.compile(target) val m1 = p1.matcher(text) val ss = SpannableString(text) if (m1.find()) { val start = m1.start() val end = m1.end() movementMethod = LinkMovementMethod.getInstance() ss.setSpan(object : ClickableSpan() { override fun onClick(widget: View) { listener?.onLinkClick() } }, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE) if (color != 0) { ss.setSpan(ForegroundColorSpan(color), start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } if (linkBold) { ss.setSpan(StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE) } } super.setText(ss, type) } interface Listener { fun onLinkClick() } }
attr.xml
から要素を取得しその値に応じてリンクさせたい文字列や見た目を設定していきます。
target
はリンクさせたい文字列、color
はリンクさせたい文字列の色、linkBold
リンクさせたい文字列がBoldかどうか
を指定します。
attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LinkableTextView"> <attr name="link_color" format="color"/> <attr name="target" format="string"/> <attr name="link_bold" format="boolean"/> </declare-styleable> </resources>
上記した3つの要素を指定しています。
使い方
xmlで置くときに3つの要素に値を設定します。
<LinkableTextView android:id="@+id/id" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:text="text" android:textColor="@color/your_color" app:target="target" app:link_color="@color/your_color" />
コードでリンクをクリックした際の動作を書きます。
binding.linkableTextView.text = "text" binding.linkableTextViewlistener = object: LinkableTextView.Listener { override fun onLinkClick() { //do something } }
僕の行動指針
行動指針 Advent Calendar 2016 - Adventar17日目の記事です。
行動指針を書くとなるとなかなか難しいですね。 ということで紹介していきます。
行動指針
自分の常識を疑う
世の中いろんな人がいるので自分の常識と違う人はたくさんいます。そういった人に時には怒りや苛立ちを覚えることもあると思います。そんな時は自分の常識はあくまで自分だけの常識であり、間違っているのは自分ではないかとまず意識するようにしています。常識なんて人の数だけあるので、もはや常識とは?くらいの気持ちです。
反対意見こそ感謝
反対意見は賛成意見より言うのが難しいと思います。人の考えを否定するわけですから簡単には言えないですよね。じゃあなんで反対意見を言うのかというと物事を良い方向に進めるためです。そのために自分の考えに反対してくれるってありがたいと思います。後輩からの反対意見は特に感謝だと思います。先輩に「そうっすね」って言っておけば良いのにわざわざ反対するわけです。自分が先輩のときはそうゆう心構えを持つようにしています。
セルフ・ハンディキャッピングに気をつける
セルフ・ハンディキャッピング - Wikipediaという言葉があります。試験前に「俺勉強してないわ〜」というような「自分○○なんでこれはできないんですよ」と先に逃げ道を作っておくアレのことです。これをやっても何も良いことはありません。苦手なことや難しいことに直面したときは言い訳して逃げ道を作るのではなく、何も言わずにただやれば良いだけです。
おたくになろう
おたくになるほど好きなものがあるって素晴らしいですよね。刹那的な生き方もあると思いますが、好きなものに継続的に情熱を捧げていく方が良いのかなと思っています。
以上です。行動していこう。