以前kazma-sさんから頂いたプログラムを読み直していて疑問があります。
言語分析のためにmekabを使い、入力した文章を品詞ごとに分ける際に、品詞の部分以外の言葉のみを使いたかったため
strtokが必要だとわかりました。改行の部分を省くために「\n」を使うのはわかりるのですが、もう一つの「\t」は何を省くために使ったのでしょうか?
mecabからの単語は
words[n] = strtok(buf, "\t\n");
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答4件
0
この回答は質問者が理解可能なレベルを超えていますが、他の人が見たときに参考にするために回答しています。
strtokが必要だとわかりました。
**MeCabを使って形態素を取り出すのにstrtok
は必要ありません。**MeCab内で各ノードはすでに分割されており、それぞれのノードへアクセスするためのAPI(ライブラリの関数)があります。strtok
等で再度結果を分割する必要はありません。もし、そのようなことが必要な場合は、正しいAPIを使用していない事になります。
参考: MeCab ライブラリ
以下は、Cで、各ノードを一行ずつ表示するサンプルです。
C
1#include <stdio.h> 2#include <mecab.h> 3 4 5int main(void) 6{ 7 char *str = "今日の天気は晴れです。"; 8 9 mecab_t *mecab = mecab_new2(""); 10 if (mecab == NULL) return 1; 11 12 const mecab_node_t *node = mecab_sparse_tonode(mecab, str); 13 if (node == NULL) return 1; 14 15 for (; node != NULL; node = node->next) { 16 if (node->stat == MECAB_NOR_NODE || node->stat == MECAB_UNK_NODE) { 17 fwrite(node->surface, sizeof(char), node->length, stdout); 18 printf("\n"); 19 } 20 } 21 return 0; 22}
mecab_sparse_tonode()
でMeCabに文字列を分析するように依頼し、最初のノードを受け取ります。node->surface
がそのノードの文字列ですが、後に続くノードも後ろにくっついています。そのため、node->length
で長さを取得して、その分までの文字列を使うようにする必要があります。node->next
は次のノードを示し、最後のノードはNULLを示すようになっています。
CのAPIはC++のAPIのラッパーですが、CのみでもMeCabは扱う事ができますので、C++の知識は不要です。
「以前kazma-sさんから頂いたプログラム」がどのようなコードだったかは知りませんが、たぶん、ノード単位で使うことを想定していなかったのだと思われます。ノード単位でアクセスしたい場合は、ノード毎に取得できるAPIを呼び出す必要があります。それらも含めてライブラリのドキュメントに書いてありますし、サンプルにも簡単な例が書いてあります。他人から貰ったコードをそのまま使うのではなく、あくまで参考とするのが普通です。ライブラリのドキュメントをよく読んだ上で、利用可能な部分は使う、利用できなさそうな部分は別の物に起きかけるなど、自分でできるようにならないと、プログラムが完成することはないでしょう。
なお、\t
が何かというとことに答えないのは、この質問が、XY 問題になってしまっているからです。
【追記】
例示のCのコードについてエラーになると言われたので、なぜ、エラーになる場合があるのかについて解説します。なお、CやC++の基礎的な知識が前提となりますので、あらかじめご了承ください。
例示のCのコードをC++としてコンパイルした場合、コンパイラの種類やバージョン、および、オプションによっては、
- 問題なくコンパイルできる。
- 警告が表示されるがコンパイルできる。
- エラーとなりコンパイルできない。
の3パターンがあらわれます。これは文字列リテラルに関する歴史的経緯とその後のC++の対応によるものです。
まず、C++はCを拡張した作られた言語ですが、CがそのままC++として必ず動くわけではありません。C++はCの完全上位互換ではなく、Cとは非互換な部分があります。今回はその非互換な部分の一つが現れた結果です。.
"abc"
のような文字列リテラルの型は、Cでのchar[N]
です。N
はそのリテラルの終端null文字を含む大きさになります。しかし、文字列リテラルを構成する各文字を変更することは未定義な動作です。そこで、C++では、文字列リテラルの型はconst char[N]
とconst
修飾子によって保護するようになりました。(Cが作られた当初は、const
修飾子そのものがなかったため、保護するという作りにしませんでした。その後の互換性から未だにchar[N]
のままです。)
賢明な読者の方々であれば、const
あり型の値をconst
なし型の値に代入することはキャストをしないとできないことはご存じかと思います。つまり、C++ではconst char[N]
をchar *
に代入することはできないと言うことです。しかし、Cとの互換性のためにC++03までは特別に文字列リテラルからchar *
へ代入することは許容されていました。C++11になって、そのような例外を認めず、明示的にキャストしない限り認めないとなったのです。
このことが複雑な事情を産みました。C++03までしか対応していない古いコンパイラであれば問題ありませんが、C++11に対応したコンパイラの場合、仕様を厳密に適用するのか、警告で済ますのか、仕様を無視するのかの選択が生まれ、コンパイルオプションによって選択できるようにしました。そのため、コンパイラの種類、バージョン、および、コンパイルオプションによって、コンパイルができたりできなかったり、警告やエラーが出る出ないも変わってくると言うことです。
なお、char s[] = "abc";
と書く場合はC++11以上でも問題ありません。なぜなら、この場合は文字列リテラル部分は配列の初期化子として扱われ、char s[] = {'a', 'b', 'c', '\0'};
相当の動作になるからです。これも、C/C++をご存じであれば、知っていて当たり前のことですね。
このようにCとC++は似ているようで、結構な違いあります。自分が書いているコードがCなのか、C++なのか、また、Cとしてコンパイルしているのか、C++としてコンパイルしているのかを正しく区別できないと、C/C++を扱うのは難しいです。特に、Visual StudioのC++コンパイラは、拡張子によってCなのかC++なのか扱いが変わるという動作がデフォルトになっており、さらに混乱することになるでしょう。
投稿2020/08/02 02:41
編集2020/08/04 10:03総合スコア21739
0
C++
1#pragma comment(lib, "libmecab.lib") 2#include <mecab.h> 3#include <stdio.h> 4 5int main() { 6 char buffer[] = "タバコを吸います。"; 7 MeCab::Tagger* tagger = MeCab::createTagger(""); 8 const char* result = tagger->parse(buffer); 9 printf("[%s]\n", result); 10 return 0; 11}
実行結果
[タバコ 名詞,一般,*,*,*,*,タバコ,タバコ,タバコ を 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ 吸い 動詞,自立,*,*,五段・ワ行促音便,連用形,吸う,スイ,スイ ます 助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス 。 記号,句点,*,*,*,*,。,。,。 EOS ]
「タバコ」と「名詞,...」の間の区切り文字は '\t' です。
「...,タバコ,タバコ」と「を」の間の区切り文字は '\n' です。
「を」と「助詞,...」の間の区切り文字は '\t' です。
「...,ヲ,ヲ」と「吸い」の間の区切り文字は '\n' です。
MeCab でのパース結果を見て、単語だけ取り出そうとするとき、
分割したときの区切り文字が何か知りたいなあと思いませんでしたか?
追記
質問者が頑張れば理解可能な strtok の使用例。
C
1#include <stdio.h> // puts, printf 2#include <string.h> // strcpy, strtok 3 4int main() 5{ 6 const char *str = "タバコ\t名詞,一般,*,*,*,*,タバコ,タバコ,タバコ\n" 7 "を\t助詞,格助詞,一般,*,*,*,を,ヲ,ヲ\n"; 8 char buf[1024], *p; 9 10 strcpy(buf, str); 11 puts("-- \"\n\" --"); 12 p = strtok(buf, "\n"); 13 while (p) { printf("[%s]\n", p); p = strtok(NULL, "\n"); } 14 15 strcpy(buf, str); 16 puts("-- \"\t\" --"); 17 p = strtok(buf, "\t"); 18 while (p) { printf("[%s]\n", p); p = strtok(NULL, "\t"); } 19 20 strcpy(buf, str); 21 puts("-- \",\" --"); 22 p = strtok(buf, ","); 23 while (p) { printf("[%s]\n", p); p = strtok(NULL, ","); } 24 25 strcpy(buf, str); 26 puts("-- \",\t\n\" --"); 27 p = strtok(buf, ",\t\n"); 28 while (p) { printf("[%s]\n", p); p = strtok(NULL, ",\t\n"); } 29}
なぜ、strcpy を何度も実行しているのか分かりますか?
strtok は元の文字列に '\0' をたくさん書き込みます。
そのことは理解できていますか?
追記2
strtok について説明します。
char str[] = "abc:123,def:45,ghi:678."; という文字列があったとします。
char *p = strtok(str, ",."); を実行すると、
strtok は、str の先頭から ',' または '.' を探しに行き、
str[7] に ',' が入っているのを見つけて、そこを '\0' に書き換えます。
"abc:123" という文字列を作ったことになります。
そして、'a' の入った str[0] のアドレスを返します。
その時 strtok は 'd' の入った str[8] のアドレスを内部に憶えておきます。
次に、p = strtok(NULL, ".,"); を実行すると、
strtok は文字列をもらえず、NULL をもらったので、先ほど憶えておいた
str[8] のアドレスから ',' または '.' を探しに行き、
"def:45" という文字列を作って、その先頭の文字のアドレスを返します。
これを繰り返すと、str は "abc:123\0def:45\0ghi:678\0" となります。
このように、元の文字列 str は書き換えられてしまうので、
今度は別の区切り文字、例えば ':' で分割しようと思ってももうできません。
だから、char buf[1024]; strcpy(buf, str); で str の文字列を buf に
コピーして、それを分割しているのです。
また、元の文字列が、const char *str = "abc:123,def:45,ghi:678."; だった場合、
str は配列ではなくポインタですから、文字列の実体は書き換えできない
文字列リテラル "abc:123,def:45,ghi:678." であり、ポインタがそれを指します。
strtok(str, ",.") とすることはできません。
この場合も、書き換え可能な char配列に strcpy でコピーしてから、
strtok を使うことになります。
投稿2020/08/01 14:35
編集2020/08/03 11:29総合スコア8224
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/01 15:10
2020/08/02 00:57
2020/08/02 06:58
2020/08/03 05:20 編集
2020/08/03 07:57
2020/08/03 13:16
2020/08/03 13:38
2020/08/03 13:51 編集
2020/08/03 14:48 編集
2020/08/05 15:53 編集
0
\t は正規表現のタブ文字に相当します。
投稿2020/08/01 11:54
総合スコア7337
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/01 11:57
2020/08/01 12:01
0
「\t」... タブコードです。
投稿2020/08/01 11:53
総合スコア6385
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/01 11:59
2020/08/04 08:23
2020/08/04 12:15
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/02 07:00
2020/08/02 08:30
2020/08/02 08:32 編集
2020/08/02 14:14
2020/08/02 16:04
2020/08/03 03:19 編集
2020/08/03 03:41 編集
2020/08/03 05:07 編集
2020/08/03 05:30
2020/08/03 09:46
2020/08/03 10:33 編集
2020/08/03 13:13