チューリングマシンはどう偉大なのか、チューリングはどう偉大なのか

現代哲学キーワード (有斐閣双書キーワード)

現代哲学キーワード (有斐閣双書キーワード)

こちらの本を先日買いました。

我々計算機屋などが煙に巻かれがちな哲学のキーワードについて、対立する概念を対照させるスタイルを主としてよくまとまっている良書のように思われます(流石に専門でないので断言はできない)。

ですが、チューリングチューリングマシンについて触れている所で、少し気になる記述がありました。

彼はチューリングマシンのアイディアを提示するだけではなく計算機として実装し,現在の計算機の基盤を築いた。(p. 31)

この部分については、執筆者の意図と逆に、チューリングの過少評価につながりかねない記述です。

「アイディア」「実装」という語が意図している所を私が誤解しているかもしれませんが、チューリングマシンの偉大なところは(たとえばいわゆる「ノイマンの草稿」とは違い)proof of conceptなど必要ないと自明なほどきっちりと形式化されていることであり、その実装はトリビアルな作業に過ぎません(万能チューリングマシンや停止問題といった応用も重要ですがここでは略す)。

チューリングマシンという計算モデルを提示したことと、それが(こんにちのコンピュータであたりまえの概念となっている)プログラム内蔵方式などの原形を含んでいたということは、別々のこととして評価されるべきです。

また、チューリングが関わった実機のコンピュータについてはいずれも、数理論理的な点すなわちチューリングマシンとの関連よりも、実践的な面に注目すべき点があると指摘されています。

まず暗号解読について。先日公開された映画の最後のあたりにあった字幕による解説にも誤解がありましたが、暗号解読機械 Bombe はチューリングマシンとは全く違う機械で無関係と言ってよく(先行機であるポーランドの bomba にオリジンがあると考えるべきか微妙な点もありますが)、星野力先生が著しているように、むしろ我々が無条件に所与のものと考えているシーケンシャルなディジタル計算機に縛られない発想があるという評価があります。

また戦後の ACE についても(英語版Wikipediaですら要出典が多く実際にこころもとない記述が多くて悩みますが)、1977年に The Computer Journal 誌に掲載された The other Turing machinehttp://dx.doi.org/10.1093/comjnl/20.3.269 )などにあるように、「チューリングマシンのアイディアを」「計算機として実装し」たもの、という表現は当たりません。(一方で、あまりそれが有名でない、という事実は、後世への影響は、たとえば英国のマシンでは EDSAC やその設計者ウィルクスなどのほうが大きかった、ということでもあります)

OS X で NASM を使っている人は、バージョン 2.11.08 を回避してください

こちらのバグ、Bug 3392306 - Issue with relative addressing and data section ( http://bugzilla.nasm.us/show_bug.cgi?id=3392306 )のために、OS X で NASM 2.11.08 はまともに使えません。

Homebrew を使っている場合は nasm21106 で 2.11.06 が入りますのでそちらを使いましょう。あるいは git から最新版の NASM も使えるかもしれません(試してない)。

松屋ジェネレータジェネレータによる、よりカオスなメニューの生成

この記事は(略

松屋の思い出

学生時代、東小金井駅前の松屋ができる前から、武蔵小金井松屋まで歩いて行ってはよく食べていたものでした。食券制に慣れてしまいうっかり宝華で無銭飲食(勘定忘れ)をやりかけたことが一度あります。ですが国分寺ではスタ丼(「すた丼」ではない)からんぷ亭に行くことがもっぱらでした。らんぷ亭のとうふ麺はいつか復活してほしいものです。

松屋ジェネレータジェネレータ

作ったのは、松屋ジェネレータtoshi-a.hatenablog.comのようなものですが、松屋のウェブページからデータを取ってきて、ジェネレータのためのデータも自動生成させています。ソースコードは以下。

部分ごとに解説します。

open('index.html'){|file|
  file.each_line {|line|
    if (line =~ %r!\A\<noscript\>\n\z!)..(line =~ %r!\A\</noscript\>\n\z!) then
      if m = %r!\A\<li\>\<a href\=\"\/menu\/([^/]+)\/!.match(line) then
        dirnames << m[1]
      end
    end
  }
}

index.html から各メニューへのリンクの情報を取り出しています。最初は REXML で解析しようとしたのですがvalidでないということでエラーになるので諦めました。

begin
  Dir.mkdir 'menu'
rescue Errno::EEXIST
  STDERR.write "directory already exist: ignored\n"
end
path_base = Pathname('menu')
dirnames.each {|dirname|
  sub_path = path_base + dirname
  begin
    sub_path.mkdir
  rescue Errno::EEXIST
    STDERR.write "directory already exist: ignored\n"
  end
}

ダウンロード先にするためのディレクトリを作っています。

dirnames.each {|dirname|
  ofile_path = path_base + dirname + 'index.html'
  url_str = "http://www.matsuyafoods.co.jp/" + ofile_path.to_s.gsub(Pathname::SEPARATOR_PAT){'/'}
  pid = spawn({}, ['/usr/bin/fetch', 'fetch'], '-o', ofile_path.to_s, url_str)
  Process.waitpid pid
  sleep(10+rand(10))
}

各サブメニューのHTMLを fetch で取ってきます。

menus = []
Dir.glob('menu/*/index.html').each {|path|
  open(path){|file|
    file.each_line {|line|
      if m = %r|\A\<h4\>(.+)\<\/h4\>\<\!\-\- [1-9][0-9]* \-\-\>\n\z|.match(line) then
        menus << m[1]
      end
    }
  }
}

やはりad hocな感じでメニュー文字列を取り出します。一番長いのは「肉カレーうどん(プレミアム牛めし使用)ミニプレミアム牛めしセット」でしたが、そんなメニューが実在するのですね。

bigram = {}
menus.each {|menu_str|
  last_char = :START
  menu_str.each_char {|c|
    bigram[last_char] ||= {}
    bigram[last_char][c] ||= 0
    bigram[last_char][c] += 1
    last_char = c
  }
  bigram[last_char] ||= {}
  bigram[last_char][:FIN] ||= 0
  bigram[last_char][:FIN] += 1
}

全てのメニュー(文字列)について、「ある文字の次に、別のある文字が現れるのはいくつか」をカウントします。順方向のバイグラムという奴です。

本格的な自然言語処理では、ここで事前処理を頑張るわけですが、ここでは手抜きをして、生データのまま、次の生成に使ってしまいます。

rand_gen = Random.new
loop {
  current_char = :START
  loop {
    next_hash = bigram[current_char]
    total = next_hash.values.inject :+
    r = rand_gen.rand total
    keys = next_hash.keys.sort_by! {|c|
      if c == :FIN then
        Float::INFINITY
      else
        c.ord
      end
    }
    keys.each {|k|
      r -= next_hash[k]
      if r < 0 then
        current_char = k
        break
      end
    }
    break if current_char == :FIN
    print current_char
  }
  puts
}

分析の逆で、「この文字の次に現れる文字はこれとこれとこれで、確率は...」というデータにもとづいてメニュー文字列を生成しています。実際に生成させてみると、たとえば次のような感じになります。

オリジナルカレミニ牛めしポンソージエッグW定食
おろし
きつねうどんミアム牛めし
肉カルビ丼
大根お子
きつねうどん
肉使用)
プレミアム牛皿<選べる小鉢>
豚汁変更
ブラス
オリルチキム牛肉カレーグセット
焼鮭
お新香
とろろし
納豆(プレミニラウング定食
プレートマト
とろたま
オリジルチキンバーうどん
きつねうどん(関西風だし)<選べる小鉢>
ビ焼肉カレーグセット
生野菜100000000%使用)
プレミナ豚テト
みそ汁
肉使用)
ビビ焼鮭
ミナ豚テト券
ミニ牛めし
焼肉定食
プラ焼定食
オリジナルチセット
大根お子
肉使用)ミアムカレンソーうどん(関西風だし
冷やっこ<選べる小鉢>
ネギ付)
焼きつねうどん(プレーうどん(プレミアム牛めしセット券
とろたまト
ビビビ焼肉使用)<選べる小鉢>
ソーセッグW定食
ビビ焼肉カレーうどん(関西風だし)
アム牛めし)ミニプレーグセー
定食
牛めしプレミアムチキムチキン酢牛めしセーグ定食
プレー

カッコの特別扱いとかをしていないので、対応していないカッコがちょっと不気味ですね。あと100000000%とかいう変なパーセントが出ています(逆に "生野菜10%使用" とかいうと残り90%はなんなんだ、という感じですね)。

温故知新話

この記事は Independent Advent Calendar 2015 の記事です。

古い本を読んでいると、ひょんな記述に出会うことがあります。そんな話をひとつ。

高橋秀俊先生編の岩波『パラメトロン計算機』という本があります( http://www.iwanami.co.jp/.BOOKS/00/7/0058200.html )。

パラメトロン計算機

パラメトロン計算機

近年はネット上にもそこそこ情報は多く、一次文献も一部は上がっていますが多くは二次三次の情報であり、まとまった一次情報としては同書が、まず当たるべき一次(ほぼ一次)文献と言っていいでしょう。和田先生のブログ「パラメトロン計算機」に時折エントリのあるパラメトロンの話題の背景として理解が深まるような情報があることも多いです。

ところで同書の204ページに、PC-1やPC-2の周辺機器ではありますが、パラメトロンと特に密接な関係にあるわけでもない鑽孔紙テープリーダーについて「各メーカー等の設計には疑問がある」として、以上のような図と、ちょっとした解説があります。同書は専門書ですが、ここの記述はどちらかというと(文体は固いものの)内容的には名エッセイ集『物理の散歩道』シリーズのおもむきがあります。

いわく、既製品などは皆、図の左の (a) のように集光レンズ(コンデンサレンズ。キャパシタではない)を付け、また、フォトセンサを紙テープの孔の直下に位置させるような設計としているが、これでは紙テープに孔が無い時と開孔時とで、センサへの入力の光量の差が少なく、しきい値の調整がクリティカルとなり、紙の色や質で微妙な調整が必要となってしまう。

右の (b) のように、レンズなど付けず点光源からの直線的な光線を利用し、センサを紙の下にフトコロを広く取って配置すれば、紙テープの孔が無い時には紙を透過する際に光は散乱光となり、センサに届く光量の減衰が大きい。一方孔が開いている時には光線が直接届く。このようにすれば、センサのしきい値の設定に余裕ができ、動作も安定する。PC-1の入力用には、このように改造して良好である。

と、だいたいそのようなことが書かれています。

もし紙テープリーダを作る必要があったら、実際にそのように違いが出るものか実験してみたいな、と思っていますが、残念ながら今まで機会がありません。

Rubyのファイバーは遅いか?

この記事は Independent Advent Calendar 2015 の記事です。

ruby fiber で検索すると「遅い」というサジェストが出てきたりするようですが、実際にそれほど重いものでしょうか?

そもそも比較対象がよくわからない点もありますが、コルーチン的な「並列には動かない並行処理」のサンプルで、スレッドの場合と比較してみました(そもそもスレッドのほうが圧倒的に重いだろ、という所ではありますが、一応どの程度か見る意味はあると思います)。

作成したサンプルは、こちら https://gist.github.com/metanest/c7bbf960e5d7a0cf963d に置きました。

timeで計測すると、

$ time ./sample_fiber.rb

real    0m1.868s
user    0m0.676s
sys     0m1.176s
$ time ./sample_thread_q.rb

real    0m12.835s
user    0m9.388s
sys     0m7.504s
$ time ./sample_thread_w.rb

real    0m11.854s
user    0m8.054s
sys     0m7.494s

という感じで、手元のFreeBSD(amd64)ではスレッドの5倍程度はファイバーの方が速い、という結果になっています。

また、並行処理として同じように動作しない可能性があるのでここには挙げませんでしたが Thread.pass を使ったものについても同程度の傾向となっていますので、この差はほぼ、ファイバーの transfer と、スレッドの切り替えとのオーバーヘッドの差と見て良いものと思われます。

マクロプロセッサ(の自作)のすすめ

「マクロプロセッサ自作のすすめ」、ではありません(宣伝 http://tatsu-zine.com/books/fpga

この記事は Independent Advent Calendar 2015 の記事です。

前提

杉浦 K. さんによる「m4 チュートリアル」や、拙作「M4入門」といった、テキストマクロプロセッサM4の解説は既にあるわけですが、この記事ではもう少しメタな話、マクロプロセッサの選び方とかそういう話をしたいと思います。

Cプリプロセッサ

少し古いタイプの、一応C言語も(普段は他の言語を使っていても)一通り使えるタイプのプログラマには一番馴染み深いのがCプリプロセッサかもしれません。Cプリプロセッサについては、松井潔さんによるmcppにより、概念の整理や良い実装とドキュメントがありますので、そちらをまずは参考にするのが良いでしょう。

一方でCプリプロセッサでは、入力を取り込むトークンの単位が、C言語の標準で「プリプロセッサトークン」として定められているものであるように、C言語の、字句(字句レベルの)構文(lexical syntax)とそれなりに緊密に結びついているものであって、汎用のテキストプロセッサではない、と考えるべきでしょう。

Lispマクロ

Lispのマクロは、リストであらわされた構文木という既に構造のあるものを、構造から構造へ変換するものですから、ここで扱いたいテキストマクロとは異なります。

アドホックスクリプティング

実際のところ一番多いのがこの、その時その時にテキスト処理したい内容に応じて、sedawkテキストエディタのマクロによるプログラミングで作業をこなす、というスタイルではないでしょうか。ここで、アスキーの『MS-DOSを256倍使うための本 vol.3』にあるsedによるroffや、sedでlispを作ってしまうような人には私から申し上げるようなことは何も無いでしょう。それはともかくとして、以前はawkの鬼門のひとつだったバイナリの操作も、C言語ではなくPerlや、もっと現代的なスクリプティング言語で扱えるようになりましたから、テキスト以外にも応用が効くなど柔軟性の高い解でもあります。

M4

しかし、もう少しテキスト処理に特化した解はないのか、というわけでM4です。が、詳しいところは最初に挙げたチュートリアル等を読んでみてください。

「汎用」と言われてはいますが、マクロ呼出しの構文が macro_name(arg1, arg2, ...) というようなスタイルに限定されていて*1、出力側はまだしも、入力側はC言語と同様な構文に限定されますので、あまり自由度がありません(なおCプリプロセッサと同様に、このような形になっていても、マクロとして定義されていない名前であればスルーします)。

またこのような構文のせいで、どこからマクロ名が始まっているのかが純粋に文字だけからはわからないため、正規表現で表現すると [A-Z_a-z][0-9A-Z_a-z]* というようなパターンにマッチする範囲をまとめてトークンとして読むという、純粋にテキストベースのマクロプロセッサではなく、Cプリプロセッサのようなトークンベースっぽい動作が含まれてしまっています(マクロのトークンベース・テキストベースといった話は前述のmcppのドキュメント等を参考にしてください)。

以上のような問題点はありますが、どのUnixにもまず存在し(多くのフリーなBSD系のシステムであれば、パッケージ等のインストールの必要なく /usr/bin/m4 があるでしょう)、実装もノウハウも枯れているM4を使う、というのは悪い選択ではない、と思います。

マクロプロセッサ自作

というわけで(やっと)マクロプロセッサ自作についてです。

前述のように、M4にはいまいちの点があるわけで、他にも自作のメリットとして「自分で作っているので、何か謎な時に簡単に内部に潜って追いかけることができる」といったことがあります。というわけで、自作についてのアドバイスをいくつか書きます。

まず最初に作るものとしては、前述の普段作っているようなアドホックスクリプトをベースとして、汎用っぽくなるように改造したもので良いでしょう。他に、もし最初から作るのであれば、awk使いならJon Bentleyさん(名著『珠玉のプログラミング』の著者です)がawkで書かれたm1というマクロプロセッサも良いでしょう。ネット上では http://www.drdobbs.com/open-source/m1-a-mini-macro-processor/200001791 に解説があります。書籍ではオライリー『sed & awkプログラミング 改訂版』の13章の最後で紹介されている良作です。

本格的自作

基本的な部分はM4と共通な、あるいはM4に対抗できるようなマクロプロセッサの自作について考えます。GNU m4 は長らく 1.6 が開発中ですし、うまくすれば新デファクトスタンダードを取れる可能性もあるかも?

M4では、マクロ定義についても「新しいマクロがその場で定義される」という副作用を持つマクロ(通常の(副作用を持たない)マクロは、置き換えが起きるだけ)、という扱いになっていて、クウォート(エスケープ)やコメント以外「全てはマクロ」というのが徹底しています。このようなM4のようなスタイルのマクロプロセッサの作り方としては、『ソフトウェア作法』の8章が参考になるでしょう(名前的にM4の先祖であるM3というマクロプロセッサが、同書の影響で作られたとも、同書がその影響を受けているとも言われています(資料によりまちまちでどちらなのかよくわからない))。ただし前述のように、M4の微妙にトークンベースの挙動などについても同様ですので、M4を越えるつもりならばもう一歩進む必要があります。なお、深く比較してみると同書が解説している内容とM4とは、マクロ名からその展開の骨組み(ボディ)を得ているタイミングが違うなど、微妙に違う点がけっこうあります。

というわけで筆者が自作したのがMacr055です。略が「M5」になるのはちょっと畏多いのと、名前がカブりまくるだろうと考えて、「M55」になるようにしました(あと、macrossという綴りに数字を当てた形にもしている)。awk千行野郎としてはawkで書かねばだろう、と思って私はawkで書きましたがかなり壁も感じるので、もっと高機能な近年のスクリプト言語を使ったほうが良いかもしれません。

実は、M4よりもさらに歴史を遡る、Christopher StracheyさんのGPMことGeneral Purpose Macrogeneratorは、その名の通り汎用さが意識されていて、マクロ呼出しではマクロ名にプレフィックスを付ける、という、完全にテキストベースで動作できる設計になっています。GPMについては、和田先生による一連のブログエントリ(http://parametron.blogspot.jp/search/label/Christopher StracheyのGPM)や、原論文 http://dx.doi.org/10.1093/comjnl/8.3.225 が参考になるのですが、和田先生のブログはともかく(?)原論文は半世紀前に書かれたものですからコの業界の時間感覚では完全に古文書であり「解読」は少々骨かもしれません……が、とはいえ論文中の記述によれば(メモリが極小だった当時のテクニックがふんだんに使われたのだろうとはいえ)最初のバージョンは(機械語で)たった250命令(250 orders、機械語命令をorderと呼ぶのは古代語)だったということでそう大きなプログラムでもなく、内容も章ごとにほぼ完全に分かれていますから全てを解読する必要もありません。

*1:なお、UNIX Programmer's Supplementary Documentsに収録のドキュメントThe M4 Macro Processorの概要で、このような記法を「functional notation」と呼んでいるのはともかくとして、そのような記法を使う言語という意味で「functional languages」という、@esumii先生に怒られそうな表現があったりします。著者はC言語K&Rと同じお二人ですが、このころはまだプログラミング言語の理論等には詳しくなかったのでしょう……(たぶん)。

Visulan、その可能性

この記事は Independent Advent Calendar 2015 の記事です。

Visulan 自体の解説については id:dagezi さんによる「マイナー言語 Advent Calendar 2013」の参加エントリー「Visulanを実装している話」( http://dagezi.hatenablog.com/entry/2013/12/15/040425 )を参照してください。ここでは、私がどのあたりに魅力を感じているか、という点を示してみたいと思います。

まず、コンピュータについての教育の根本的(ファンダメンタル)な部分として、プログラミングの教育があるべきだ、という点について、私はほぼ、Viscuit の原田 康徳さんの「プログラミングを学ぶべきたった一つの理由 - ビスケットのあれこれ」に賛同しています。そして話は少し飛びますが、Viscuit にグリッド的なものがないのは(Viscuit の基本的なコンセプトとしてアナログ的な所があるわけですが)、拡張機能として考えがないわけでもないとのことでしたが、ある程度大規模にシステマティックなものを作ろうとすると壁になるでしょう。

私の想像ですが、Viscuit があえてそのような設計にしているのは、Visulan が既にあるからではないかと思います(名前も似てますね)。というわけで、以下では「Visulanらしい」プログラムを紹介します。

実装としては、前述の id:dagezi さんによるウェブアプリが http://dagezi.github.io/visulan-js/public/index.html にありますので、それを利用させていただくことにします。URLの # 以下で、プログラムを入力することもできます(「Link」というボタンを押せば、URL文字列として出てきます。JavaScriptですので、基本的に手元のブラウザ内で動いていることになります)。なお、Visulan には「3D-Visulan」という実装(名前の通り3次元に拡張されている)もあります( http://www.vector.co.jp/soft/win95/prog/se026552.html )。3D-Visulan は ICC でアートとして展示されたこともあり( http://ascii.jp/elem/000/000/331/331422/ )、プログラミング言語そのもの自体が美術になりうる、ということの実現可能性もまた Visulan にはありますが、ここではそちらにはあまり深く踏み込みません。

セルラーオートマトン

ひとつめのサンプルは、セルラーオートマトンです。頑張ればライフゲームのような2次元のものも作れそうな気もしますが、もしかするとライフゲームライフゲームを実装する(ライフゲームの世界8 http://www.nicovideo.jp/watch/sm19509968 で解説されています)のに近い手の込んだことが必要になるかもしれません。

ここでは1次元のセルラーオートマトンで、(自明なものを除くと)最も単純で簡単だと思われる「ルール90」を実装してみました。

隣のパターンと干渉するため、3段階に分けて次の世代を生成しています。おおざっぱに各段階を説明すると次のような感じになりますが、全く「見たまま」という言葉通りですから、実物をよく見たほうが理解が早いかもしれません。

  • ある世代の白の「生きている」セルから、次の世代の計算のために、左右に水色と赤のパターンを分配する
  • 水色のパターンと赤のパターンから、「そこから前の世代を見ると左右両方が生きている」=黄色、「左右の片方が生きている」=青と紫、というパターンを作る
  • 青と紫のパターンから、次の世代のセルを作る。この時、黄色が存在すると(その場合、ルール的にその場所のセルは生存にならないので)白のセルの生成が防がれる

という感じで、ルール90を実装しています。

リンクはこちらになります(左上の「Play」というボタンの操作で動きます) http://dagezi.github.io/visulan-js/public/index.html#comp/Po1_AxhO38MUi1kkhzXs93_BhRWo-KaAXmFWrXfKlMFdC-fR510-qQBbAATo1Lcx4hIzLgREuRNlJF8lR2WTVmrlIJh1Wg3CmCQfSCeBm9hm0tGiAHg569bb2Dumv3P-7X2-BgEegYGeeNahPlKgVqYiwVHyMaIARqkumUmG4biR2TaJ7AU59EUl2qS6fhWaxqQWmeW1ZTXQAC7tTS1auTj5PcmtgyrNNSPifXlZE9xSXaQLrmOzDOOoNKgrqyHoRN47lXTbhzLDp5xT_TMXaCkgqSKPB7f-6yCbL6_IV9hf34htidXkCAcdzmC7ODIT8ITC1tD4RpEUijHDUcU7hiEW9sbtcXjMbDCUSlMRyRTKVTrliSTBIEAA

「夢の」O(n)ソート

「夢の」とカッコを付けているのは(現状では非常に突破困難なブレークスルーが可能なことを前提としている)夢のSSTO(単段軌道宇宙船)みたいな意味で、ここでは、Visulan 自身の実行のために2次元のパターンマッチが必要なこと(いろいろ高速化手法はありそうですが)を意識しています。スパゲティソート(がO(1)だ、という主張)のようなイメージだと思ってください。

このサンプルは最初に示した id:dagezi さんによる解説からリンクがある原文献に例示があるものですが「Visulanの可能性」のひとつをわかりやすくデモできるもののひとつではないかと思います。これを「ソート」と言っていいのか、いわゆる「比較によるソート」ではない点はともかく途中経過ではデータとして不整合があることなど、微妙な点もありますが、初期状態と最終状態を見る限りではちゃんとソートになっていますし、考え方としては交換ソートを目一杯並列化したものと見ることもできるように思います。

リンクはこちらになります http://dagezi.github.io/visulan-js/public/index.html#comp/Po2jGZdO38MUslxvZYHs93_BhRxGI4AXlsOZpsvQ401LTmcx51ywnd_wMR8YwwWPFV4oiTK7SUMSrIFLm8iIuUJV8HY3XtoerVGObQZizynnTdseUeIdT2HoOT7XkJY6PfVha-7ry2YHqUAQz-zuHB1nDSZi6R3tGuunFwITZGYU4RfhluKYXU5YaJYYE-qapR2cWKqdRNSi2esEnhPlkdDSVRBbVlHR7dQRWtdZPVpjGNFQvl7VPjNRnDIytByeb-e9Ptm0trob21k_XD16s1fcs9B8f1p67rF9OXLzPbU_9XR5WZ7bOpgypdDauG4HHZ_FzNI6aEFfcGRJQfKYLGExGbYnpGdFNXoo3ErcGdERQ45HMlE6EkgEFIEk2Go5nkiFUi4g5m88n4nYRekE-kvdmcynQOg_dG0m60nlw0GPVakvmtSWY2W4554tlqt58hEC6EamFc6WBPXGumKt5GkXWm3suVs7UjXl6oUGlUCpF3G1E-VIqUJHW22Finkc4NRmNByOhj3yyN0_kSsVkpaJpPu85m72J01bXUlstpr1RhYfQtVosh8WV2NZov1y3WOvVl1ZnNu4st3Op_O5Lu6nuNlW5_vD6e89bYuf8oNTofNpfz0KLjc49Wttuzne4hdjuflwuHitHtcn_fT89Ns_r6_UW-Xlf71cD69Pt8_sE9s-QE_q-W7AQ2353v-R6gTY44wVB8HQdBsFVEhG4PvWyH_qhkLYQBa74dht4oYh4FEUGJE4WRNEUZRvB0e-tF0Xq4wkOxHGcVxeC5CYfFMOAQAAAA==