[React] WebRTC x React でP2Pビデオ通話アプリを作ってみる #1
概要
最近見つけたWebRTCという技術と最近勉強したかったReactを使ってP2Pビデオ通話アプリを作ってみたいと思います。
今回すること
- そもそもWebRTCって何?
- どんなものを作るのか?
ここらへんをはっきりさせておきたいと思います。
そもそもWebRTCって何?
WebRTC (Web Real-Time Communications、ウェブリアルタイムコミュニケーション) は、ウェブアプリケーションやウェブサイトにて、仲介を必要とせずにブラウザー間で直接、任意のデータの交換や、キャプチャしたオーディオ/ビデオストリームの送受信を可能にする技術です。 WebRTC に関する一連の標準規格は、ユーザーがプラグインやサードパーティ製ソフトウェアをインストールすることなく、ピア・ツー・ピアにて、データ共有や遠隔会議を実現することを可能にします。
つまり、ブラウザ間での直接データ交換を可能にする技術という感じです。
しかもほとんどのブラウザに標準で入っている機能なのでjavascriptやTypescriptで記述するだけで使えちゃいます。
大体次の様なステップで使うみたいです。(かっこの中は使うメソッドなど)
ビデオとオーディオの取得 (getUserMedia)
ピア接続 (RTCPeerConnection)
どんなものを作るのか?
今回は簡単なビデオ通話アプリを作成します。 部屋指定をして、同じ部屋にいれば1対1でビデオ通話ができるアプリを目指します。 余力があればチャット機能も実装したいと思います。
次のページ→ [React] WebRTC x React でP2Pビデオ電話アプリを作ってみる #2 ~準備編~ - やってみたらなんとかなる
[SwiftUI] RealmSwiftのMigrationについて
概要
長い間見て見ぬ振りしてきたRealmSwiftのMigration処理について触れることになりました。
とてもとても長い道のりでした。
もっと早くにやっておけばよかったと後悔してます。
解決策
まずは解決策から
class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { RealmMigrator.setDefaultConfiguration() return true } }
これを<アプリの名前>App.swiftに追記します。
次にRealmMigrator.swiftを作成して
import SwiftUI import RealmSwift enum RealmMigrator { static private func migrationBlock( migration: Migration, oldSchemaVersion: UInt64 ) { if oldSchemaVersion < 1 { // 変更点とか書くところ } } static func setDefaultConfiguration() { let config = Realm.Configuration( schemaVersion: 1, migrationBlock: migrationBlock) Realm.Configuration.defaultConfiguration = config } }
と書き込みます。以上です。
ここに辿り着くまでにほんとに時間かかった...
その他解説
執筆中...
関連記事
まとめ(?)
- Migrationは早めにやっておこう
- いつの間にか消えていたAppDelegate.swift
参考にしたサイト
[SwiftUI] Cannot assign to property: 'last' is a get-only property について
# 概要 今回はSwiftUIをいじっていた時に出てきた
Cannot assign to property: 'first' is a get-only property
というエラーについて分かったことと対応策です。
エラーが起きた状況
var a = [1, 2, 3, 4] a.last! = 3 // Cannot assign to property: 'first' is a get-only property
という配列の最後の要素をlastで取り出して変更しようとした時にエラーが起きました。
どういうやらこのエラー「'last'というpropertyはgetしかできません」という意味みたいです。
つまり、'last'というプロパティは値を取ってくるだけで、新しい値をセットすることはできないということみたいです。
そのため、「最後の要素を取ってきて」「新しい値をセットする」という二つの手順を踏みたい場合には'last'は使えないです。
解決策
この問題は単純に配列の要素をIndexで指定して取り出すことで解決できます。
var a = [1, 2, 3, 4] a[a.count - 1] = 3
'count'で配列の長さを取得して、-1で最後の要素を指定して値を取り出すという感じです。
まとめ
- 'last'は配列の最後の要素を取り出すことしかできない
- 取り出した要素を変更したい場合は直接Indexで指定する
- 'count-1'で最後の要素のIndexになる
補足
'first'でも同じエラーが起こるみたいです。この場合は
var a = [1, 2, 3, 4] a[0] = 3 // a.first! = 3から変更
で修正できます。
sortされたlistを維持したいならbisectをつかう! -AtCoder学習日記 #6
概要
今回はABC217-Dについてです。TLEから抜け出せずに終わってしまいました...
なかなかTLEにならないコードを書くのは難しいですね。
今回はレートが下がりました悲しいです。
ACらないコード
l, q = map(int, input().split()) cut = [] for _ in range(q): c, x = map(int, input().split()) if c == 1: cut.append(x) else: cut_p = list(filter(lambda s: s <= x, cut)) cut_n = list(filter(lambda s: s > x, cut)) if len(cut_p) == 0: if len(cut_n) == 0: print(l) else: print(min(cut_n)) else: if len(cut_n) == 0: print(l - max(cut_p)) else: print(min(cut_n) - max(cut_p))
切断部分を記録しておいて、長さを取得する際は含まれていて欲しい部分より「大きい最小値」と「小さい最大値」の差を出力する。
と言った考え方です。
解説読んだ感じだと、考え方自体は間違っていなかったみたいですね...
解決策について
どうやら、解き方は間違っていない模様ですが、Listに挿入するときに少し工夫をすることでTLEを脱出できそうです。
今回はPythonのbisectを使ってみようと思います。
bisectライブラリとは
Pythonの二分探索を用いた標準ライブラリです。少ない計算量でListをソートされた状態を保つことができます。
詳しいことは参考文献に書いてあります。今回はbisect_leftを使うことになりそうです。
bisect_leftはソートされた状態を保ったまま挿入する場合のindexを返してくれるメソッドです。
ACれるコード
from bisect import bisect_left from array import array l, q = map(int, input().split()) cut = array('i', [0, l]) for _ in range(q): c, x = map(int, input().split()) index = bisect_left(cut, x) if c == 1: cut.insert(index, x) else: print(cut[index] - cut[index-1])
listではTLEのままだったので、arrayを使用してみたらうまくいきました。
もしかしてarrayの方が早い...?
まとめ
- 順序を保ったまま挿入したい場合はbisectを使おう!
- listよりもarrayを使った方が高速みたいです。
- 今回はレートが下がりました悲しいです。
参考文献
Dequeを使うといいのかも -AtCoder学習日記 #5
概要
今回はABC216-Dについてです。 今回はうまくいったと思ってたんですけどね...やはりTLEに悩まされます。
ACにならなかったコード
n, m = map(int, input().split()) nodes = [set() for _ in range(n+1)] for _ in range(m): k = int(input()) pipes = list(map(int, input().split())) for i in range(k): for j in range(i+1, k): nodes[pipes[i]].add(pipes[j]) if pipes[i] in nodes[pipes[j]]: print("No") exit() print("Yes")
筒の中で出てくる色の順番が異なる部分が出てきたらダメだと思って組んだんですけどTLEになってしまいました...
解説の解説
解説を私なりに要約すると「書いてあることをそのままやればいいよ。ただ計算量(実行時間)に気をつけてね」ということみたいです。
なので今回は前から気になっていたDeque型を使ってみたいと思います。
Dequeについて
Deque とは、スタックとキューを一般化したものです (この名前は「デック」と発音され、これは「double-ended queue」の省略形です)。
ちなみに、Listでも同じことが可能です。しかし、Dequeを用いた方が圧倒的に早い模様です。それぞれの計算量は以下のようになってます。
ListとDequeで計算量が変わるところは赤くしています。
操作 | List | Deque |
---|---|---|
先頭に追加 | O(n) | O(1) |
最後尾に追加 | O(1) | O(1) |
先頭を取り出す | O(n) | O(1) |
最後尾を取り出す | O(1) | O(1) |
先頭についての操作はDequeの方が上手みたいですね。
ACなコード
from collections import deque n, m = map(int, input().split()) pipes = [] for _ in range(m): k = input() pipes.append(deque(map(int, input().split()))) queue = deque([(q.popleft(), i) for i, q in enumerate(pipes)]) # queueに各筒の先頭の球を入れる。 balls = [-1] * (n + 1) # 入ってた筒と球が2つ出たかどうかの判定 while queue: a, i = queue.popleft() if balls[a] == -1: balls[a] = i # 1つ目の球なら球の入っていた筒の番号を保存 else: j = balls[a] balls[a] = -2 # 2つ目の球なら2つ出たことを記録して # jに1つ目の球が入っていた筒の番号 # iに2つ目の球が入っていた筒の番号を記録 if pipes[j]: queue.append((pipes[j].popleft(), j)) #もしj番目の筒に球があるならqueueに入れる if pipes[i]: queue.append((pipes[i].popleft(), i)) #もしi番目の筒に球があるならqueueに入れる if sum(balls) == -2 * n - 1: print('Yes') else: print('No')
今回からコードの説明をコメントで書き込んでみました。少しはわかりやすくなっている...はず...です。
まとめ
- 先頭と最後尾を扱う配列の場合はListよりもDequeの方が計算量が少ない!
- 問題によっては問題文通りに進めるのも良いこともある
参考文献
最長増加部分列ってどちら様??? -AtCoder学習日記 #4
概要
今回はARC125-Cについてです。
今回の問題に至っては問題文の理解から時間がかかりました。最長増加部分列(LIS)についての問題のようです。
私事:やっと茶色に乗ることができました!!!
最長増加部分列とは?
部分列
そもそも最長増加部分列を理解する前に、部分列について知る必要がありそうです。
部分列とは、「ある数列のうち、いくつかの項を取り出して順番を変えずに作れる数列」のことです。
例えば数列を考えると、は部分列ですし、も部分列です。
途中の項を飛ばしてもOKですが順番を変えるのはNGです。
そのため、は部分列ではありません。
増加部分列
次に増加部分列の説明です。
増加部分列は「となる部分列」のことです。
例えば数列を考えると、は増加部分列ですし、も増加部分列です。
すべての項において常に増加するため、は増加部分列ではありません。
最長増加部分列
最後に最長増加部分列についてです。
最長増加部分列は「増加部分列の内、最長のもの」です。
先程の例の数列だとが最長増加数列です。
ACになるコード
n, k = map(int, input().split()) a_list = list(map(int, input().split())) ans = [] used = [0] * (n + 1) i = 1 for a in a_list[:-1]: ans.append(a) used[a] = 1 while i < a: if used[i] == 0: ans.append(i) used[i] = 1 i += 1 break i += 1 for j in range(n, i - 1, -1): if used[j] == 0: ans.append(j) print(" ".join(map(str, ans)))
指定された数列を入れつつ、入れられるところは小さい数字から入れていくって感じですね。
その後、余った数字は単調に減少するようにします。
まとめ
- 最長増加部分列は「増加部分列の内、最長のもの」
- 再帰的に考えてみるのもあり
- やっと茶コーダー!
参考文献
[Python] 数値誤差恐るべし。。。 -AtCoder学習日記 #3
概要
今回はABC215-Bについてです。
コード自体は簡単なのですが「誤差」でWAになってしまいました。
ACできないコード
import math n = int(input()) print(math.floor(math.log2(n)))
ほとんどACなのですが一部だけWAになってしまっています。
なぜ誤差が出るのか
そもそも誤差はなんで生まれるのかについてです。
整数を扱っている際には特に誤差は生じないのですが、問題は小数を扱っている時です。
通常コンピュータでは10進数を2進数に変換して計算します。
そのため2進数にすると循環小数になってしまうような10進数を扱うと、循環している部分が丸められてしまうため正確な値にならないのです。
誤差を起こさないための対策
10進数同士の和差積商であれば、pythonのdecimalモジュールとfractionsモジュールを用いることで解決できるようです。参考文献のサイトに例が載っているのでそちらを参照してみてください。しかし、
floatの計算→Decimalの計算→Fractionの計算、の順番に計算速度が遅くなります。
とのことですので、競プロにはあまり向かない気がします。
解説にもあるように
競技プログラミングにおいて、整数性のある問題に対して整数性を外して実数で処理することには危険が伴います。整数性のある問題はなるだけ整数型で処理することをおすすめしますが、どうしても実数型が必要な際は注意して取り扱いましょう。
を忠実に守るのが良さそうです。。。
ACできたコード
import numpy n = int(input()) n = numpy.array(n).astype('float128') s = numpy.log2(n) print(s.astype('int64'))
Numpyの型変換で4倍精度浮動小数点型(float128)を用いることでACになりました。
まとめ
- 小数の計算するときには誤差に気をつける。
- decimal, fractionsモジュールを使うことで誤差をなくせる
- そもそも整数を扱う問題を実数に落とし込むのは危険