byh01337
@byh01337 (Geng Tanaka)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

(Perl)ファイルから隣接リストを読み込んでハッシュを作成したいのですが、ソースに記した場合との違いに気づけないでいます。

解決したいこと

セットされた環境がありましたので、試したい計算によくPerlを使っています。

広井誠 (Makoto Hiroi) 様のHP
「経路の探索」
http://www.nct9.ne.jp/m_hiroi/linux/perl10.html
で公開されているサンプルを元に、以下のソースファイルを作成しました。

 公開されているサンプルでは、隣接リストをソースの中で

%adjacent = (
A => ['B', 'C'],
B => ['A', 'C', 'D'],
C => ['A', 'B', 'E'],
D => ['B', 'E', 'F'],
E => ['C', 'D', 'G'],
F => ['D'],
G => ['E']
);

としてあります。これをファイル「adjacent.txt」から読み込むものにしたいのですが、エラーこそせないものの出力がありません。

 随所で変数をprintしてみるのですが、%adjacent をソースに書き込んだ場合とファイルから読み込んだ場合との差異に気づけないでいます。お気づきの点をご指摘いただけましたら幸いです。

発生している問題・エラー

(出力なし)

該当するソースコード

# dfs.pl : 経路の探索 (深さ優先探索)
#
#          Copyright (C) 2015 Makoto Hiroi
#
use strict;
use warnings;
use Encode qw/encode decode/;

# 隣接リスト
my %adjacent = (); 
my $filename = "adjacent.txt"; 
open(IN, "$filename") or die "Can't open `$filename': $!"; 

while(my $line = <IN>){
    decode('utf-8', $line);
    chomp ($line);
    my @arr = split("\t", $line);
    my $key = shift(@arr);
    if (exists($adjacent{$key})){
        ;
    }
    else{
        @{$adjacent{$key}} = ();
    }
    my @array = @{$adjacent{$key}};
    push(@array, $arr[0]); 
    $adjacent{$key} = \@array;
}

close (IN); 


# 実行
dfs('G', ['A']);

# 配列に同じ要素があるか
sub member {
    my ($n, $xs) = @_;
    foreach my $x (@$xs) {
        return 1 if $x eq $n;
    }
    0;
}

# 深さ優先探索
sub dfs {
    my ($goal, $path) = @_;
    my $x = $path->[-1];
    if ($goal eq $x) {
        print "@$path\n";
    } else {
        my $ls = $adjacent{$x};
        foreach my $y (@$ls) {
            if (!member($y, $path)) {
                push @$path, $y;
                dfs($goal, $path);
                pop @$path;
            }
        }
    }
}

「adjacent.txt」

A   B
A   C
B   A
B   C
B   D
C   A
C   B
C   E
D   B
D   E
D   F
E   C
E   D
E   G
F   D
G   E

望まれる出力結果

A B C E G
A B D E G
A C B D E G
A C E G

0

4Answer

adjacent.txt の改行が CRLF だと問題が再現しますが、 LF なら問題なく動作します。これは macOS において chomp ($line); が LF しか除去しないため、 CR が行末に残るせいです。

adjacent.txt をの改行コードを LF に変換して保存するか、または chomp ($line);$line =~ s/[\r\n]+$//; に置き換えて CRLF も除去できるようにすれば解決します。

2Like

Comments

  1. @byh01337

    Questioner

     どう考えてもファイルを正常に読み込めていないように思い、もしかして…と思い当たったところでした。再現性のご確認までありがとうございました。

     トラブルの解決がもちろん大切なのですが、
    「 chomp ($line); を $line =~ s/[\r\n]+$//; に置き換えて CRLF も除去できるようにすれば」、
    このような考え方を見せていただけることが、本当に大きく後に効いてきます。

    改行コードの違いが問題であったことを確認しました。ありがとうございました。

adjacent.txtのアルファベット間のスプリッタがスペースで構成されているため,adjacent.txtのアルファベットの間の文字をタブに置き換えるか,またはmy @arr = split("\t", $line);でのタブ指定をスペース指定に変更する必要があります.エディタによってはタブを押してもスペースが挿入されるような設定もあり得ますので,確認をお願いします.

また,直した上で実行したら27行目でエラーが出たため,以下のように直す必要があります.

修正案
  my @array = @{$adjacent{$key}};
- push(@array, @arr);
+ push(@array, @arr[0]); 
  $adjacent{$key} = \@array;

今まではsplitで分けることができず,@arrが配列になっていなかったのでエラーではありませんでしたが,先述の修正を行うと@arrは配列扱いになり,0番目を取り出してあげることで目的の動作をするようになります.

1Like

Comments

  1. @byh01337

    Questioner

      ありがとうございます。

     このページに張り込んだ際にタブが空白にされてしまうらしく、ただいまadjacent.txtのスプリッタはタブであることを確認しました。

    ( my @arr = split("\t", $line);
    の後に、
    for my $element (@arr){
    print $element, "\n";
    }
    print "\n";
    として、セパレートを確かめました。
    出力→
    A
    B

    A
    C

    B
    A

    B
    C

    B
    D






        
    - push(@array, @arr);
    + push(@array, @arr[0]);
    を行いましたが動作は変わらずでした。
    (adjacent.txt で後に
    A<tab>B<tab>C
    などもあり得ると思い、
    push(@array, @arr);
    が良かろうと考えたものでもあります。たしかPerlで許される操作であったと思いました。)
  2. 謎ですね,こちらでは先述のadjacent.txtの修正ののちに,

    -------------------
    key = A
    B
    C
    -------------------
    key = B
    A
    C
    D
    -------------------
    key = C
    A
    B
    E
    -------------------
    key = D
    B
    E
    F
    -------------------
    key = E
    C
    D
    G
    -------------------
    key = F
    D
    -------------------
    key = G
    E
    a-A
    b-A B
    a-A B
    b-A B C
    a-A B C
    b-A B C E
    a-A B C E
    b-A B C E D
    a-A B C E D
    b-A B C E D F
    c-A B C E D F
    d-A B C E D
    c-A B C E D

    〜中略〜

    b-A C E D F
    c-A C E D F
    d-A C E D
    c-A C E D
    d-A C E
    a-A C E
    b-A C E G
    A C E G
    c-A C E G
    d-A C E
    c-A C E
    d-A C
    c-A C
    d-A

    という出力結果が得られていますが,これは意図したものではないのでしょうか.
  3. 失礼,どうやら
    @arr[0]
    と直すのではなく
    $arr[0]
    とこちらでは直していました.
    ですが確かに,@arrのままでも同上の結果が得られます
    また,意図されている答えである
    A B C E G
    A B D E G
    A C B D E G
    A C E G
    も中略で消えている箇所はありますが確かに出力が確認されています.(念の為byh01337さんのコードを再度コピペして実行しましたがちゃんと出力されました).「出力がない」とは具体的にどういう状態なのでしょうか,この前,無限ループに入ったためプログラムが終了せず結果が表示されてないことを「出力がない」と仰っていた方もいらしたので,念の為確認させてください。
  4. @byh01337

    Questioner

    出力なし、と書いたのですが、変数確認のためにprintを複数箇所に入れたものをアップしておりました。ただいま編集してそれらを削除したものをアップしてあります。お手間をとらせたこと申し訳ありません。

    @PondVillege様の環境では
    A B C E G
    A B D E G
    A C B D E G
    A C E G
    が出力されている様子ですね…。

    無限ループではありません。プロンプトがきちんと表示されます。しかしこちらでは、出力としての表示は何も現れない状態です。
  5. 変数確認のためのprintを削除いただいた後のコードをこちらでコピペして実行しましたが,やはり意図されている出力そのままが得られますね,バージョンを示しておきます.
    Perl v5.30.3
    MacOS Monterey v12.1
    Perlでは大幅にバージョンが違うわけでもないので構文による差ではなさそうですが...
  6. 出力が行われない条件として,グラフがきちんと構成されていない可能性しか思い浮かびません.
    adjacent.txtの末尾に改行を入れてしまっているのでは等も考えましたが,その場合にはエラーが吐き出される上,最初にいただいた返信できちんと値を受け取れているようでしたので考えからは除外しました.
    念の為,以下のコードをclose(IN);の下にでも入れてグラフの構成を確認願いたいです(ここのコメント欄の仕様上,半角スペースは削除されるので全角スペースで置換してありますのでコピペは非推奨です).
    for my $k ('A', 'B', 'C', 'D', 'E', 'F', 'G') {
      for my $element ($adjacent{$k}) {
        print $k, " -> ", @$element, "\n";
      }
    }
    ちなみに私はこれで
    A -> BC
    B -> ACD
    C -> ABE
    D -> BEF
    E -> CDG
    F -> D
    G -> E
    と出力されます.(その次の行から意図した結果が得られます.)
    問題がなければお手上げです.
  7. @byh01337

    Questioner

     誠にありがとうございます。いただきました

    for my $k ('A', 'B', 'C', 'D', 'E', 'F', 'G') {
      for my $element ($adjacent{$k}) {
        print $k, " -> ", @$element, "\n";
      }
    }
    をclose(IN);の下に挿入して、実行した結果は次のとおりでした。

    C -> B
    D -> A
    E -> A
    F -> B
    G -> C
    F -> D
    G -> E

    左端がアルファベット順になっていない時点で???となり、いま頭を悩ませています。
  8. 左側がアルファベット順になっていないのは本当に謎です,
    差分について^が置換部分,/が不足している文字だとすれば,比較すると
    C(^A) -> B(/C)
    D(^B) -> A(/CD)
    E(^C) -> A(/BE)
    F(^D) -> B(/EF)
    G(^E) -> C(/DG)
    F -> D
    G -> E
    になってますね...$kはA,Bを飛ばして2つずれて始まっているのに$adjacent{$key}からはAから始まる順に取り出されています.

    原因がそこにらへんにありそうですね,
    また$adjacent{$k}で取り出した$elementには1つしか入ってないことで,pushが1度しか効いていないことを察することができます.ちなみにpush(@array, @arr);でも動作しました.
    念の為,一度@arrayと取り出すのをやめたいので,
    #my @array = @{$adjacent{$key}};
    #push(@array, @arr);
    #$adjacent{$key} = \@array;
    push(@{$adjacent{$key}}, @arr); # 代わりにこう書く
    で試していただけますでしょうか.ちなみにこの書き方でも動作しました.

    また,%adjacent全体において,別々の$keyに挿入されている可能性もあるため,全部吐き出させるために
    while (my ($key, $value) = each(%adjacent)) {
      print $key, " -> ", @$value, "\n";
    }
    と実行してほしいです.順番はおかしくなりますが,先述の出力と同じ結果が出ます.
  9. @byh01337

    Questioner

    uasi様にご指摘いただきました通り、改行コードの違いが問題でした。思い返してもCRが混ざる機会に思い当たらないのですが、現にそのケースでした。お騒がせをいたしました。

    トラブルそのものは解決できてもちろん嬉しいことなのですが、このような際にどんなことを試すべきか、そのTipsをPondVillege様に複数ご案内いただけたことが何よりの収穫です。

    たいへんありがとうございました。
  10. こちらにコピペして再現しないのは完全敗北でした,反省します.
    お疲れ様でしたぁ...

perlはもう5年以上書いてないので自信ないのと、古い版での動作が分かりません(そこまでトリッキーな書き方はしてないはず)。paiza上(5.30.0)では望み通りの結果となってるため、共有しておきます。

一応ソースも添付します。念のため参考元のページからmemberdfsのサブルーチンはコピーし直してます。

既に指摘されてる点は省きますが、配列やハッシュの取り扱いにはクセがあるのでその点留意下さい。また差異は自分で調べ研究して下さい。

Main.pl
#!/usr/bin/perl

use strict;
use warnings;
use utf8;
use Encode;

# 隣接リスト
my %adjacent;
my $filename = 'adjacent.txt';

open IN, '<:encoding(UTF-8)', "$filename" or die "Can't open `$filename': $!";

while (my $line = <IN>) {
    $line =~ s/[\r?\n?]*$//;

    my ($key, $row) = split "\t", $line;

    $adjacent{$key} = [] unless exists $adjacent{$key};

    push @{ $adjacent{$key} }, $row;
}

close IN;

#
# dfs.pl : 経路の探索 (深さ優先探索)
#
#          Copyright (C) 2015 Makoto Hiroi
#

# 配列に同じ要素があるか
sub member {
    my ($n, $xs) = @_;
    foreach my $x (@$xs) {
        return 1 if $x eq $n;
    }
    0;
}

# 深さ優先探索
sub dfs {
    my ($goal, $path) = @_;
    my $x = $path->[-1];
    if ($goal eq $x) {
        print "@$path\n";
    } else {
        my $ls = $adjacent{$x};
        foreach my $y (@$ls) {
            if (!member($y, $path)) {
                push @$path, $y;
                dfs($goal, $path);
                pop @$path;
            }
        }
    }
}

# 実行
dfs('G', ['A']);


出力結果(paiza.io上・2022年2月3日時点)
A B C E G
A B D E G
A C B D E G
A C E G
1Like

 もしかするとソースに異常が無く、なにか環境に由来する問題があるのかもしれないと思いまして、当方の環境を記します。
macOs Catalina バージョン10.15.7
ターミナル バージョン2.10(433)
$ perl -v
This is perl 5, version 18, subversion 4 (v5.18.4) built for darwin-thread-multi-2level

0Like

Your answer might help someone💌