Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hi Monks,
I have an array of strings, typically (and much simplified) :

/var/www/data/stuff
/var/www
/var/www/data/misc
/var/logs
/var/logs/data

I need to turn this array into a multidimensional array or hash so I can traverse and output it tree-style, for ex:
var
 -> www
    -> data
        -> stuff
        -> misc
 ->logs
    -> data


Any concise (golfed!) code much appreciated.

Replies are listed 'Best First'.
Re: directory listing to array tree
by ELISHEVA (Prior) on Sep 14, 2009 at 13:27 UTC

    You don't need a multi-dimensional array to do that. You could get the same effect without the complexity by taking advantage of the regular syntax of Unix name:

    • sort list ascibetically
    • loop through each path in order. When outputing each item,
      • use split to convert each path into an array of names, e.g. ['var', 'www', 'data', 'misc']
      • use size of array to determine amount of indentation, e.g. 4 path components = 3 tabs, 3 path components = 2 tabs, and so on.
      • display only the last array element on each line after the space.

    Presto, there you have your output. I'm not posting code on purpose. Without context for why you are interested in this question, it smells a lot like homework or a search for hints in a golf contest. If I'm providing help for that sort of thing, I generally like to know that I'm doing so, so I can make a judgment about how much help is too much (or too little) help. Perhaps you could add a few words about why you care about this?

    Best, beth

Re: directory listing to array tree
by toolic (Bishop) on Sep 14, 2009 at 13:28 UTC
Re: directory listing to array tree
by ww (Archbishop) on Sep 14, 2009 at 13:13 UTC
    "Any concise (golfed!) code much appreciated"

    Perhaps, better, show us what you've tried, and tell us how it fails. The Monastery will serve better to help you learn than as a code-writing service (which it isn't!).

Re: directory listing to array tree
by ikegami (Patriarch) on Sep 14, 2009 at 17:43 UTC
    use strict; use warnings; use Data::Dumper qw( Dumper ); sub add { my $p = \shift; $p = \($$p->{$_}) for @_; } my $tree; while (<DATA>) { chomp; add $tree, m{[^/]+}g; } print(Dumper($tree)); __DATA__ /var/www/data/stuff /var/www /var/www/data/misc /var/logs /var/logs/data
    $VAR1 = { 'var' => { 'www' => { 'data' => { 'misc' => undef, 'stuff' => undef } }, 'logs' => { 'data' => undef } } };

    Golfed:

    perl -MData::Dumper -ne'END{print Dumper$t}chomp;$p=\$t;$p=\($$p->{$_})for/[^\/]+/g'

    Or if you don't mind a trailing blank line:

    perl -MData::Dumper -nlE'END{say Dumper$t}$p=\$t;$p=\($$p->{$_})for/[^\/]+/g'
      Sorry guys, couldn't log in earlier.
      ++ikegami golf. I shall spend tomorrow trying to decypher it ; )

      Re: "show us 'yer code!" I was working backwards from the last node towards the root node but was hitting 20 lines+.
      The PHP was very similar to my perl attempt, which of course made me weep buckets.

      c
        Re: "show us 'yer code!" I was working backwards from the last node towards the root node but was hitting 20 lines+.

        Show your code anyway.

        #!/usr/bin/perl -- use strict; use warnings; use Tree::Builder; my $tb = Tree::Builder->new(); my @list = qw[ /var/www/data/stuff /var/www /var/www/data/misc /var/logs /var/logs/data ]; $tb->add($_) for @list; use Data::Dumper; print Dumper( { $tb->getTree } ); __END__ $VAR1 = { '' => { 'var' => { 'www' => { 'data' => { 'misc' => {}, 'stuff' => {} } }, 'logs' => { 'data' => {} } } } };
Re: directory listing to array tree
by Fletch (Bishop) on Sep 14, 2009 at 15:45 UTC
    (ns tree-sample (:use [clojure.contrib str-utils map-utils])) (def test-paths [ "/usr/local/bin" "/var/www/data/stuff" "/var/www" "/var/www/data/misc" "/var/logs" "/var/logs/data" "/usr/fnord" ]) (defn split-path [p] (re-split #"/" p)) (defn path-to-map [p] (let [p-rev (reverse p)] (loop [hd (first p-rev) tl (rest p-rev) acc {}] (if (empty? tl) {hd acc} (recur (first tl) (rest tl) {hd acc}))))) (defn merge-trees [tl] (loop [hd (first tl) tl (rest tl) acc {}] (if (empty? tl) (deep-merge-with concat hd acc) (recur (first tl) (rest tl) (deep-merge-with concat hd acc))))) (defn printTree ([t] (printTree t 0)) ([t indent] (let [prefix (apply str (repeat indent " ")) dirs (sort (keys t))] (loop [curdir (first dirs) rd (rest dirs)] (println (if (zero? indent) "/" (str prefix "->")) curdir) (let [ch (get t curdir)] (when-not (empty? ch) (printTree ch (+ 2 indent)))) (when-not (empty? rd) (recur (first rd) (rest rd))))))) (println (str "test data:\n\t" (apply str (interpose "\n\t" test-paths +)))) (println "tree:") (printTree (merge-trees (map path-to-map (map split-path test-paths))))

    Update: Tweaked to sort directory names.

    Update 2: And yes, I have the Perl version (which came out about ~4 lines shorter). No, it's left as an exercise for the reader. :)

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Only 4 lines less?

      By using the leading portions of the path as a hash key, the subscript separator variable, $; (see perlvar), you can preserve the exact ordering given by the OP. This also takes under 20 lines, excluding the strict and warnings declaration.

      For the OP, I don't know that language is the real winner here in getting the volume of code down. Understanding the nature of the problem itself is far more important. PHP also has a split function and str-repeat takes the place of Perl's x operator, so this code could be translated virtually as is to PHP.

      Best, beth

      Update: ikegami is right, I'm not actually using $; but rather the path subsegments as the hash key. See strike out above for correction.

        Your code doesn't use $; at all. Even if you didn't realize it, it seems you discovered that $; is useless here since you can't do $h{@segs}.

        Yup, roughly four lines shorter reimplementing (virtually) the same approach (not counting shebangs, use strict-y pragmata, or blank lines). Not to mention things like I don't yet grok Clojure / FP well enough that I can do in it what my $t = \%tree; while( @parts ) { my $head = shift @parts; $t = ($t->{$head} ||= {}); } lets me do trivially in Perl, or a better way than (apply str (repeat indent " ") to do " " x $indent.

        The Perl version is certainly much "slimmer" than my Clojure (and less . . . "chatty"? "visually cluttered"?) at roughly half the number of characters:

        $ wc * 44 152 1258 tree.clj 42 112 741 tree.plx 86 264 1999 total

        Also the OP had said they were trying to build a tree and traverse it; your above approach doesn't give a data structure that can be manipulated afterwards or walked multiple times (not that it's not shorter and yours preserves the original ordering (unlike mine), just that's what I read the OP as looking for).

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Re: directory listing to array tree
by Anonymous Monk on Sep 14, 2009 at 14:26 UTC
    Here you go , it uses Tree::Builder, It prints
    $VAR1 = [ 'var' => [ 'www' => [ 'data' => [ 'misc' => [], 'stuff' => [] ] ], 'logs' => [ 'data' => [] ] ] ];
      Fantastic.

      Coworker faced with solving the problem in PHP, I took the perl view to try to reduce his code but couldn't seem to do it.
      I'm still looking at the data strings and I'm sure theres a really concise way to do it with some trickery....

      Maybe not though

        Why don't you show your code?