Python | 株式会社Altus-Five / 株式会社Altus-Five は、技術力で勝負するシステム開発会社です。 Mon, 01 Sep 2025 00:34:56 +0000 ja hourly 1 https://wordpress.org/?v=6.8.2 /wp-content/uploads/2025/01/cropped-favicon-32x32.png Python | 株式会社Altus-Five / 32 32 10 家電デバイス再生・視聴ログ分析基盤 /works/2025/08/29/10-%e5%ae%b6%e9%9b%bb%e3%83%87%e3%83%90%e3%82%a4%e3%82%b9%e5%86%8d%e7%94%9f%e3%83%bb%e8%a6%96%e8%81%b4%e3%83%ad%e3%82%b0%e5%88%86%e6%9e%90%e5%9f%ba%e7%9b%a4/ /works/2025/08/29/10-%e5%ae%b6%e9%9b%bb%e3%83%87%e3%83%90%e3%82%a4%e3%82%b9%e5%86%8d%e7%94%9f%e3%83%bb%e8%a6%96%e8%81%b4%e3%83%ad%e3%82%b0%e5%88%86%e6%9e%90%e5%9f%ba%e7%9b%a4/#respond Fri, 29 Aug 2025 12:09:06 +0000 /?p=772 概要 家電デバイスから送信される再生・視聴ログを収集・分析する基盤の開発を担当しました。 ユーザーごとの視聴傾向を把握できるようにログを集計し、サービス側での活用を支援する仕組みです。 特徴 技術要素 弊社の対応ポイント […]

The post 10 家電デバイス再生・視聴ログ分析基盤 first appeared on 株式会社Altus-Five.

]]>
概要

家電デバイスから送信される再生・視聴ログを収集・分析する基盤の開発を担当しました。

ユーザーごとの視聴傾向を把握できるようにログを集計し、サービス側での活用を支援する仕組みです。

特徴

  • 再生履歴を集計し、ユーザー単位での視聴傾向を分析可能に  
  • AWSを活用した大規模ログの自動処理基盤を構築
  • ワークフローによる処理自動化で運用負荷を軽減

技術要素

  • AWS
    • EMR(on EC2 / Serverless)
    • Lambda
    • Step Functions
    • EventBridge
    • DynamoDB
    • OpenSearch
    • Managed Workflows for Apache Airflow
    • S3
    • CloudFormation
  • 分析基盤: Databricks  
  • 開発言語: Python / PySpark  

弊社の対応ポイント

  • データ収集・分析に適した構成を検討する技術検証から参画
  • AWSやデータ処理基盤を組み合わせ、設計・開発・構築を担当
  • 短期間での実装を行い、安定した基盤の立ち上げに貢献

成果

  • 家電デバイスからのログを安定的に収集・処理できる仕組みを構築
  • 視聴傾向を可視化できるデータを提供し、上位システムでの活用を可能に

The post 10 家電デバイス再生・視聴ログ分析基盤 first appeared on 株式会社Altus-Five.

]]>
/works/2025/08/29/10-%e5%ae%b6%e9%9b%bb%e3%83%87%e3%83%90%e3%82%a4%e3%82%b9%e5%86%8d%e7%94%9f%e3%83%bb%e8%a6%96%e8%81%b4%e3%83%ad%e3%82%b0%e5%88%86%e6%9e%90%e5%9f%ba%e7%9b%a4/feed/ 0
08 専門業者向けB2B ECサイト /works/2025/08/29/%e5%b0%82%e9%96%80%e6%a5%ad%e8%80%85%e5%90%91%e3%81%91b2b-ec%e3%82%b5%e3%82%a4%e3%83%88/ /works/2025/08/29/%e5%b0%82%e9%96%80%e6%a5%ad%e8%80%85%e5%90%91%e3%81%91b2b-ec%e3%82%b5%e3%82%a4%e3%83%88/#respond Fri, 29 Aug 2025 11:30:36 +0000 /?p=753 プロジェクト概要 専門業者向けB2B ECサイトの保守開発において、複雑なドメイン知識と最新技術を組み合わせた包括的なソリューションを提供しました。レガシーシステムの段階的リプレースと機能追加により、お客様の事業継続性を […]

The post 08 専門業者向けB2B ECサイト first appeared on 株式会社Altus-Five.

]]>
プロジェクト概要

専門業者向けB2B ECサイトの保守開発において、複雑なドメイン知識と最新技術を組み合わせた包括的なソリューションを提供しました。レガシーシステムの段階的リプレースと機能追加により、お客様の事業継続性を保ちながらシステムの競争力向上を実現し、持続可能な成長基盤を構築いたしました。

技術要素

  • Python
  • BigQuery
  • TypeScript
  • React
  • Next.js
  • CI/CD
  • 継続的デリバリー
  • AI駆動開発

プロジェクトの特徴

  • 複雑なドメイン知識への横断的対応力
    多岐にわたる専門分野を理解し統合的にシステム設計
  • レガシーシステムの段階的リプレース戦略
    事業を止めずに安全かつ効率的にシステム近代化を実現
  • 最新技術の積極的導入
    AI駆動開発など先端技術を活用した開発効率の向上
  • 非機能要件への配慮した設計
    パフォーマンス・セキュリティを重視した堅牢な基盤構築

成果

  • 事業継続性を保ちながらのシステム近代化の完遂
  • 段階的リプレースによるシステム移行リスクの最小化
  • CI/CD導入による開発・運用効率の大幅向上
  • レガシー技術から最新技術への段階的移行の成功

Altus-Fiveの強み

複雑なドメインへの深い理解力と横断的対応力、レガシーシステムの近代化ノウハウ、最新技術の積極的導入により、お客様の事業を止めることなくシステム価値を向上させる提案力と実行力を発揮いたします。技術的課題と事業課題を同時に解決する総合的なソリューション提供が私たちの強みです。

The post 08 専門業者向けB2B ECサイト first appeared on 株式会社Altus-Five.

]]>
/works/2025/08/29/%e5%b0%82%e9%96%80%e6%a5%ad%e8%80%85%e5%90%91%e3%81%91b2b-ec%e3%82%b5%e3%82%a4%e3%83%88/feed/ 0
06 データパイプラインの開発・保守/運用 /works/2025/08/29/data-pipeline-development-maintenance-operation/ /works/2025/08/29/data-pipeline-development-maintenance-operation/#respond Fri, 29 Aug 2025 08:39:50 +0000 /?p=748 データパイプラインの基盤移行・統合と運用ルール整備を実施。可視化やリカバリ設計により安定したデータ運用環境を構築。最新技術を活用し、保守・運用効率化と品質向上を実現したプロジェクトです。

The post 06 データパイプラインの開発・保守/運用 first appeared on 株式会社Altus-Five.

]]>
プロジェクト概要

データパイプラインの基盤移行・統合と運用ルール整備を実施。可視化やリカバリ設計により、安定したデータ運用環境を構築しました。
最新のデータオーケストレーション技術を活用し、保守・運用の効率化と品質向上を実現しました。

技術要素

  • Python
  • dagster
  • dbt
  • Hadoop
  • Athena
  • Glue
  • Docker
  • PostgreSQL

プロジェクトの特徴

  • データパイプラインの可視化
    dagster基盤による運用状況の見える化
  • リカバリ設計
    途中からの復旧が可能な柔軟な設計
  • 保守・運用ルール整備
    安定運用のための手順・ルールを策定

成果

  • データ運用の安定化
  • 保守・運用効率の向上
  • 最新技術への対応
  • 運用品質の向上

Altus-Fiveの強み

最新技術への対応力と運用ルール策定力で、複雑なデータ基盤の移行・運用も安定してリード。顧客の業務効率化と品質向上に貢献できる点が弊社の強みです。

The post 06 データパイプラインの開発・保守/運用 first appeared on 株式会社Altus-Five.

]]>
/works/2025/08/29/data-pipeline-development-maintenance-operation/feed/ 0
Python の linter /blog/2022/11/21/python-linter/ /blog/2022/11/21/python-linter/#respond Mon, 21 Nov 2022 09:36:00 +0000 http://43.207.2.176/?p=203 python 用のプロジェクトの boilerplate を作っていて、今、盛り込んでいるのが、こういうものです。 black は、四の五の言わずに・・・というスッキリ感がいいです。mypy は、やや癖がありますが、だん […]

The post Python の linter first appeared on 株式会社Altus-Five.

]]>
python 用のプロジェクトの boilerplate を作っていて、今、盛り込んでいるのが、こういうものです。

  • black
  • flake8
  • isort
  • mypy

black は、四の五の言わずに・・・というスッキリ感がいいです。
mypy は、やや癖がありますが、だんだん慣れてきたので、型チェックのありがたさを感じてます。
他に docstring の linter (flake8-docstring)も入れてます。プロジェクトによっては、無くてもよいかもです。

ほかのチームでは、以下のツールも使われているようなので、これらも、入れないとなぁ・・・

  • astor(抽象構文木)
  • coverage
  • flake8-import-order
  • flake8-polyfill
  • freezegun(時刻)
  • parameterized
  • pydevd(リモートデバッグ)
  • pytest
  • pytest-cov
  • pytest-html
  • pytest-pythonpath
  • tox-pipenv(環境管理)
  • pre-commit(git pre commit)

The post Python の linter first appeared on 株式会社Altus-Five.

]]>
/blog/2022/11/21/python-linter/feed/ 0
PySparkの分散される処理単位であるクロージャと共有変数の仕組み /blog/2020/06/15/pyspark/ /blog/2020/06/15/pyspark/#respond Mon, 15 Jun 2020 05:32:00 +0000 http://43.207.2.176/?p=270 Spark では、処理が分散されて、複数のノードやスレッドで実行されますが、 分散先でのデータの共有方法と、分散先で実行される処理単位のクロージャについて説明します。 共有変数 Spark には、データの共有とか、集約す […]

The post PySparkの分散される処理単位であるクロージャと共有変数の仕組み first appeared on 株式会社Altus-Five.

]]>
Spark では、処理が分散されて、複数のノードやスレッドで実行されますが、 分散先でのデータの共有方法と、分散先で実行される処理単位のクロージャについて説明します。

共有変数

Spark には、データの共有とか、集約するための仕組みが備わっています。
それが、ブロードキャスト(Broadcast)と集約器(Accumulator) です。
http://mogile.web.fc2.com/spark/spark220/rdd-programming-guide.html#shared-variables

ブロードキャスト
参照専用の共有メモリのようなもので、ドライバーで共有したい変数をブロードキャスト変数として登録すると、その変数のラッパーオブジェクトが作成されて、ラッパーオブジェクトが、各エグゼキューターに配信されます。
エグゼキューターからは、ラッパーを通じて、共有変数の実態にアクセスすることができます。 このあたりは、通信のコストを下げるために効果的なブロードキャストアルゴリズムで実装されていると、マニュアルに記載されています。

集約器(Accumulator)
accumulatorは、ドライバーで初期化されて、エグゼキューターからは、加算処理だけが可能で読み取りは出来きません。 読み取りはドライバーからのみ行うことができます。
何かの件数をカウントしたり合計値を求めるような処理に使われます。

クロージャ

PySpark では、ドライバがRDDのメソッドをクロージャとしてシリアライズして、エグゼキューターに配信して、エグゼキューターでデシリアライズされて、処理が実行されます。

Python は “全てがオブジェクト” ということで、関数もオブジェクトなので、関数自体が様々な属性を持っています。
例えば、func()という関数は、func.__code__でコード自体を参照可能だし、func.__globals__で関数内で参照している global な変数がリストされます。他にも、func.__defaults__func.__dict__などもあります。 https://docs.python.org/ja/3/reference/datamodel.html

クロージャとは、関数と関数内で使用される変数などがセットになったものを指します。 ときどき、クロージャとlambda関数が同じものとして説明されている記事を見ることがありますが、違うものです。lambda関数は、無名関数がクロージャを構成するときの要素の1つというのが、正しいです。

繰り返しになりますが、PySparkは、クロージャをシリアライズして、各エグゼキューターに配信します。

Python のシリアライズモジュールには pickle, marshal, dill, cloudpickle などがあります。(※この記事もあわせて読んでください。https://qiita.com/kojisuganuma/items/e9b29e8e5ef5f5f289b2
PySpark のシリアライズモジュールは、Python標準の pickle を元にした、独自のものでした。 実際のソースコードは、こちらです。 https://github.com/apache/spark/blob/d48935400ca47275f677b527c636976af09332c8/python/pyspark/cloudpickle.py#L222
A func comprises: code, globals, defaults, closure, and dict.とコメントがあるとおり、 PySparkがエグゼキューターに配信するクロージャは、以下を要素としています。

  • コード
  • 関数内で使っているグローバル変数
  • 引数のデフォルト値
  • 引数以外の変数に対してのセル (cell) 群
  • 関数属性の名前空間

シリアライズされる変数(グローバル変数など)が持つ値は、”宣言時のスコープ”によって設定された値です。 間違いやすいのは、global変数が宣言されていて、その変数の設定を init() などで、別の関数で初期化してたりすると、たとえ、それが RDD を実行する前だとしても、配信先では、復元されないということです。あくまで、変数宣言時のスコープで行われる処理だけがシリアライズされます。

参考記事

https://qiita.com/Hiroki11x/items/4f5129094da4c91955bc#%E2%85%B3-rdd%E3%81%AE%E6%93%8D%E4%BD%9C
https://www.lifewithpython.com/2014/09/python-use-closures.html

stackoverflow にも興味深い記事がありました。
「PySparkがブロードキャストされなかった変数を参照できるのはなぜですか?」
https://stackoverflow.com/questions/33337446/why-is-pyspark-picking-up-a-variable-that-was-not-broadcast
以下、回答の訳です。

基本的には、クロージャはシリアライズされて各executorとtaskに送られ、 task実行中にアクセスできるようになるので、 RDDのスコープ外のすべての変数をブロードキャストする必要はありません。
ブロードキャスト変数は、ブロードキャストされるデータセットが大きい場合に便利です。

まとめ

ブロードキャストとクロージャの使い分け

グローバル変数の初期値としてセットして、それを RDD や UDF の関数内から、参照するように実装しておけば、クロージャとして、データも一緒にエグゼキューターに配信されるので、小さいデータは、クロージャとして実装して、大きいデータはブロードキャストの機能を使うという使い分けができます。

分散実行されるコードの境界

Javaと違って、Pythonのコードだと、分散先で実行されるコードの境界がわかりづらいのだけど、その答えは、クロージャが実行されるということでした。

ご参考までに。

The post PySparkの分散される処理単位であるクロージャと共有変数の仕組み first appeared on 株式会社Altus-Five.

]]>
/blog/2020/06/15/pyspark/feed/ 0
【Pythonでテキスト処理】Double arrayでTrieを実装してみた /blog/2017/11/13/double-array/ /blog/2017/11/13/double-array/#respond Sun, 12 Nov 2017 15:50:00 +0000 http://43.207.2.176/?p=345 Trieを実装する上で必要不可欠な、データ構造の工夫 前回のブログで、辞書検索、サジェスト、形態素解析などを実装するのに使われるTrieの概略を紹介しました。 木構造の各エッジに文字のラベルを付けることで辞書を表現すると […]

The post 【Pythonでテキスト処理】Double arrayでTrieを実装してみた first appeared on 株式会社Altus-Five.

]]>
Trieを実装する上で必要不可欠な、データ構造の工夫

前回のブログで、辞書検索、サジェスト、形態素解析などを実装するのに使われるTrieの概略を紹介しました。

木構造の各エッジに文字のラベルを付けることで辞書を表現するという抽象的な説明を与え、それを実現するLOUDSという簡潔データ構造を紹介しました。 今回はLOUDSとは別の、Double Array(ダブル配列)というデータ構造で実際にTrieをPythonで構築し、共通接頭辞検索を行えるようにします。 実装方法については 『日本語入力を支える技術』(徳永, 2012)、通称「徳永本」に準拠しますので、同著を読まれた方が実装を試みる際の参考にもなると思います。 また、Double arrayのエッセンスを紹介していきますので、参考書籍をお持ちでない方もDouble arrayによるTrieの実装について理解できることを目指しています。

前提:Trieを実装する上で、データ構造の選択は重要

まず、Trieを実装する際のデータ構造として、LOUDSやDouble Arrayといった様々な選択肢が存在する背景を紹介しておきます。 検索の高速性を担保しつつTrieを実装する素朴な方法として、各ノードからの文字ごとの遷移先をテーブルに格納しておくというやり方が挙げられます。

このようなTrieを想定してみましょう(ノードの番号は幅優先で付与)。

上記のTrieに対応するテーブルが上図です。各セルを (行ラベル, 列ラベル)という組で表すことにすると、例えば (2, E)に4が格納されていることになりますね。

これは、Trie上の「2」というラベルが付いたノードから、文字「E」のラベルがついたエッジを辿ることで、「4」 というラベルが付いたノードに遷移できることを表します。 このようなテーブルを二次元配列などの形式でメモリ上に保持すれば、例えば「AGI」という入力に対してTrie上のノード5に効率よく遷移出来ます。そのことを、Pythonのコードを用いて確かめてみましょう。

【Python】テーブルによるTrieと共通接頭辞検索ソースコード

ここからは、GitHubで公開した下記のソースコードを引用しながら解説を進めます。100行に満たないソースコードですが、『日本語入力を支える技術』で解説されている「テーブルによるTrieの実装」をそのまま実現しています。

https://github.com/msato-ok/trie-master/blob/master/tabletrie.py

まず辞書の生成ですが、’MyTrie’というクラスでTrieを表すものとし、’MyTrie’のオブジェクトが一つの辞書を表します。オブジェクトを生成する際には、語彙が格納された配列を入力します。

class MyTrie():
    def __init__(self, vocabulary): #入力は辞書式順序でソート済みであることを想定
view rawtabletrie.py hosted with ❤ by GitHub

例えば下記のようにすると、辞書を生成することが出来ます。

        self.sub_dictionaries[present_node].view_terms()

vocabulary = ['age', 'agile', 'alias', 'all', 'alt', 'altus'] #語彙
view rawtabletrie.py hosted with ❤ by GitHub

テーブルによるTrieの構築について、アイデアは既にご紹介しましたので、その具体的な実装内容はソースコードをご覧ください(’MyTrie’クラスの’init’関数の中身)。

この時、共通接頭辞検索のコードは下記のようになります。

                #suffix[0] == '#'のときはそのノード自身が単語を表す。新規ノード不要

    def common_prefix_search(self, query):
        present_node = 0 #Trieの根ノードから探索開始
        for alphabet in query:
            if alphabet in self.table[present_node]:
                present_node = self.table[present_node][alphabet]
            else:
                print('The result for query \'' + query + '\' was not found.')
                return
        #for文を抜けているので、検索結果が存在
view rawtabletrie.py hosted with ❤ by GitHub

共通接頭辞検索のアルゴリズムは、入力として‘query’という文字列を取ります。例えば下記のようなものが考えられるでしょう。

vocabulary = ['age', 'agile', 'alias', 'all', 'alt', 'altus'] #語彙

dictionary = MyTrie(vocabulary)

dictionary.common_prefix_search('altus')
dictionary.common_prefix_search('alt')
view rawtabletrie.py hosted with ❤ by GitHub

出力は’query’が表すノード下にある全ての単語です。一例として、下記が正しい出力です。

上述したリンク先のリポジトリをcloneし、「tabletrie.py」を実行して頂くと、同様の結果が確認可能です。

テーブルによる実装の弱点と、Double arrayの必要性

この実装はシンプルな上、高速な検索が可能ですが、「Trieのノード数、および使われる文字数、それぞれに比例したメモリが必要」という弱点があります。

つまり、この素朴な方法では、文字数が大きくなったり、辞書の単語数が大きくなると、メモリに乗り切らないという問題が生じてしまうのです。 そこで、「よりメモリ効率の良いデータ構造を用いれば、巨大な入力(語彙)に対しても使えるようなTrieによる辞書が実装できないか」という問が生じます。

Trieを格納・運用できるデータ構造については、検索の高速性をある程度犠牲にしてメモリ効率を上げていくというのが基本方針です。そのような「メモリ効率と高速性、両者のバランスが取れたデータ構造」を「簡潔データ構造」と呼びます。今回のテーマであるDouble array(ダブル配列)は、簡潔データ構造の一種です。

補足:Trieによる形態素解析と「文字数」の上限について

※この段落は読まなくても、理解上差し支えありません。

先ほど、テーブルによる実装は「Trieのノード数、および使われる文字数、それぞれに比例したメモリが必要」と述べました。 ここで「ノード数」は単語数に従って増大するため問題だということが伝わりやすいと思いますが、「文字数が大きくなる」というイメージは持ちづらいかもしれません。 アルファベットなら26文字、ひらがななら五十音といった風に、上限があるように感じられるので、問題がないと思われるかもしれません。 しかし形態素解析のような用途にTrieを用いたい場合は、文字数は増大の可能性があります。 形態素解析においては、「わがはいはねこである」という入力文章に対し、単語の結びつきの強さによって「吾輩は猫である」という文章と「我が杯羽根子である」という文章のどちらがもっともらしいかという判断を行います。このとき、先ほどの「文字」にあたるものは「単語」(ex. 吾輩、杯、羽根、etc.)であり、その数はアプリケーションの用途によります。 よって文字数の上限を定めることはできず、テーブルによるTrieの「文字数に比例してメモリが必要」という性質は大きな障壁となるのです。

Double arrayによるTrieの実装

Double arrayによるTrieの実装をPythonで行ったコードは、下記URLからアクセスできます(GitHubリポジトリ)。

https://github.com/msato-ok/trie-master/blob/master/double_array_trie.py

まずは、テーブルによる素朴な実装とDouble arrayによる実装はどう違うのか、簡単に対比してみたいと思います。

テーブルによる実装では、Trieのノードそれぞれについて、各文字による遷移先を示すための行(すなわち配列)を用意しました。

一方、Double arrayを用いることは、語彙(入力する単語の集合)がどんなに大きくても、2本の配列だけでTrie全体を表現することを可能にしてくれます。 「ダブル配列」という言葉からは配列の本数が倍加するような印象を受ける方もおられるかもしれません。そうではなく、「どんなに巨大な辞書でも、2本の配列だけで、対応するTrieを表現できる」ことが、このデータ構造の特色なのです。

例えば、下記が上述のTrieに対応する、Double arrayの例です。

ここでDouble arrayとは、上図にあるCheck, Baseという2つの配列のことです。2本の配列に格納された値を適切に読み込んでいくことで、上記のTrieを再現したり、共通接頭辞検索を高速に行えるようになっています。 しかし、なぜこの2本の配列によってTrieを表現出来るのか、配列そのものを見ていても理解は難しいでしょう。ですので一旦 Check, Baseが何を表すのかということは忘れることにします。

代わりに、上図のもう一つの特徴的な点に着目します。それは、Trieのノードに付与された番号が先ほどとは変わっているという点です。 これはデタラメな数値ではなく、Double arrayによるTrieの構築アルゴリズムに従って論理的に付けられた番号です。その規則を知ることで、Double arrayによるTrieの構築と、検索に用いる際のアルゴリズムが直観的に理解しやすくなります。

Double arrayにおける各ノードの番号付け規則

番号付け規則を理解するための「データ構造もどき」

少し遠回りですが、Double arrayについて理解するためのステップとして、Double arrayから幾つかの要素を省いた「データ構造もどき」について考えてみると、理解しやすくなります。 すなわち、下記のシンプルな規則に基づいてTrie(?)を構築するとどうなるか、想像してみましょう。

  • 各ノードの番号として、ノードに入ってくるエッジのラベル「A, B, C, …」を、「1, 2, 3, …」に置き換えた数値を採用する

結果は下図のようになります。

ここでは便宜的にノードとノードの間にエッジを引いて、Trieの形を保たせていますが、実際に情報として存在するのは各ノードの番号だけです。 この「図」をTrieだと考えて運用しようとすると、2つの障害が発生することを、下図で示してみました。

  1. 複数のノードに同じ番号が付けられてしまい、例えば「1」のノードから「L (12)」で遷移しようとしたとき、複数ある「12」のノードのうちどれに遷移すればいいかわからない
  2. 「1」のノードから「E (5)」で遷移しようとしたとき、遷移先は存在しない(「1」の子ノードに「5」のラベルが付いたノードはない)にもかかわらず、離れた「5」のノードに遷移できるように見えてしまう

Double arrayは、この2つの症状を克服するために工夫を加えたものと解釈することが出来ます。

正しいノード遷移を可能にするための2つの工夫

まず前述した問題1.については、ノードの番号が重複しないようにすれば解決します。 ノード間の番号の重複を解決するために、ちょっとした工夫を加えたものが下図です。

こちらは、一部のノードに「+1」もしくは「+2」という数値を持たせ(もっと大きな値を持たせてもよい)、ノードの番号を付ける際の規則を、下記のようにほんの少しだけアップデートした結果の図です。

  • 各ノードの番号として、親ノードの持つ数値に対し、ノードに入ってくるエッジのラベル「A, B, C, …」を、「1, 2, 3, …」に置き換えた数値を加えたものを採用する

ひとまずこのようにすると、ノードの番号の重複については解決出来ることになります。

上図では、「子ノードの番号に加えた量」を、親ノードの傍に付記していました。実際にメモリ上でその値を記憶しておくためには配列をよく用いますので、親ノードの番号を添え字として、配列の中に値を格納してみることにしましょう。

実はこの配列が、冒頭で天下り的に示した「Double array」の「Base」という配列に一致するものとなっています。 要は、Baseというのは、「子ノードの番号を付ける際のベースとなる量」を表す数値であり、ノードの番号を重複させないために加えるものなのですね。

もう一つの工夫は、問題2.を解決するためのものです。 Base配列を導入することでノードの番号の重複は解消されましたが、「遷移出来ないノードに遷移出来るように見える」という問題は依然として残っています。例えば:

  • ノード「0」から「E (5)」という文字によって、ノード「5」に遷移出来るように見える
  • ノード「12」から「F (6)」という文字によって、ノード「7」に遷移出来るように見える(ノード『12』は+1というBaseの値を持つことに注意)

これを解決する方法は実は単純です。各ノードに対し、親ノードの番号を覚えさせておくことで、子ノードへ遷移しようとするとき「遷移先が自分の子ノードかどうか」チェック出来るようにすれば良いのです。

このようにすると、子ノードではないノードへと遷移してしまう問題を防ぐことが可能になります。 例えば「1」というノードからは「E (5)」という文字によってノード「5」に遷移出来るように一見見えますが、ノード「5」の親は「7」です。つまりノード「5」はノード「1」の子ではありませんから、遷移は出来ないということがわかるのです。

実はこの「各ノードの親を記憶させた配列」が、Double arrayのもう一つのデータである「Check」という配列にあたるのです。

上図は最初に天下り的に示した「Double arrayによるTrie」と同じ図になっています。 Double arrayに格納されているデータが何を表すかに関する、概念的な説明は以上です。

【Python】Double arrayを構築し、共通接頭辞検索を行うコード

https://github.com/msato-ok/trie-master/blob/master/double_array_trie.py

上記ソースコードでは、与えられた語彙(単語の集合)に対し、実際にDouble arrayに基づくTrieを構築し、共通接頭辞検索を行うことが出来ます。 行数は130行程度と、tableによる実装とさほど変わりません(ただし、高速化のための工夫は行っていないことにご注意ください)。

Check, Baseへの値の入力は、MyTrieクラスのコンストラクタ内で行っています。ここでは入力語彙に基づき、Check, Baseに適切な値を格納していきます。

また、double_array_trie.pyMyTrie. common_prefix_search関数は、Check, Base二つの配列を用いて下記のような処理を行うことが出来ます。

  • 入力語彙のうち、「AL」から始まるすべての単語を出力する(共通接頭辞検索)
  • 入力語彙のうち、「ALP」や「ALA」から始まる単語が存在しないことを判定する
    def common_prefix_search(self, query):
        present_node = 0 #Trieの根ノードから探索開始
        for alphabet in query:
            if not self.base[present_node] + (ord(alphabet) - self.origin) in self.check:
                #該当列のcheckの値が存在しない
                print('The result for query \'' + query + '\' was not found.')
                return
            elif self.check[self.base[present_node] + (ord(alphabet) - self.origin)] != present_node:
                #該当列のcheckの値が現在のノードと一致しない
                print('The result for query \'' + query + '\' was not found.')
                return
            else:
                present_node = self.base[present_node] + (ord(alphabet) - self.origin)
        #for文を抜けているので、検索結果が存在
        print('The result for query \'' + query + '\' is:')
        self.sub_dictionaries[present_node].view_terms()
view rawdouble_array_trie.py hosted with ❤ by GitHub

Trieをシステム開発に応用する

弊社はフルスクラッチの開発案件を得意としており、Trieやそれに類するデータ構造を用いて、共通接頭辞検索を応用した機能を柔軟に実装することが出来ます。 例えば、「求人案件情報の検索において、全文検索に基づくサジェストを出したい」と言った要求は、「Java」という入力に対して「Java」や「Java エンジニア」、「JavaScript」といった出力を返すことなので、共通接頭辞検索を行うことに他なりません。 「できない」と思えるような技術課題について、技術力を駆使してお応えする準備がございます。まずはお気軽に、開発案件のご相談を頂ければ幸いです。

また、様々なデータ構造を活用して、顧客の課題を解決する最善の方法を模索したいエンジニアからの求人応募も常にお待ちしております。ぜひ「採用情報」をご覧になってみてください。

The post 【Pythonでテキスト処理】Double arrayでTrieを実装してみた first appeared on 株式会社Altus-Five.

]]>
/blog/2017/11/13/double-array/feed/ 0