challenge 文字列からの情報抽出

与えられた文字列から特定の条件を満たす文字列を抽出するコードを書いてください。 状況としてはテキスト形式で渡された原稿の中から、画像のファイル名を抽出するようなものをイメージしてください。

サンプル入力

aaa abc-hidden.png>hoge-big.jpeg
---foo-hidden-small.gif|^_^a.bmp
--hiddena-hoge.png<=not hidden~~
--small.jpg<=not small(^_^)
normal-small-big.hoge

サンプル出力

name:'abc', ext:'png', size: normal hidden: True
name:'hoge', ext:'jpeg', size: big hidden: False
name:'foo', ext:'gif', size: small hidden: True
name:'a', ext:'bmp', size: normal hidden: False
name:'hoge', ext:'png', size: normal hidden: False
name:'small', ext:'jpg', size: normal hidden: False
name:'small', ext:'hoge', size: big hidden: False

探すべき文字列は下の条件を満たします

  • アルファベットと1個のピリオド、ハイフンで構成される
  • 前後にはアルファベットではない文字がある(abcd.jpgがaaaabcd.jpghogeなどと書かれていることはない)
  • ピリオドの後ろは拡張子で、アルファベットだけで構成されている
  • ピリオドの直前に-hidden, -small, -bigがある場合には特殊な意味がある。複数個ある場合(a-hidden-big.jpgなど)も同じ
  • ファイル名に-hiddenと-smallまたは-hiddenと-bigの両方が含まれる場合は-hiddenの方が先にある
  • 特殊な意味の-hidden, -small, -big以外でハイフンが使われることはない
  • 特殊な意味の-smallと-bigの両方が付くことはない

出力は以下の条件を満たす必要があります

  • ファイル名が出現した順に表示される
  • ファイル名に-hiddenが含まれるかどうかを真偽値で表示する
  • ファイル名に-smallまたは-bigが含まれる場合はsmallまたはbigと、含まれない場合はnormalと表示する
  • -hidden, -small, -bigを取り除いたファイル名部分と、拡張子を表示する

このお題は、正規表現のグループに名前をつけて連想配列として取得できるPythonからの挑戦状です。

Posted feedbacks - Nested

Flatten Hidden
Gaucheには名前付きサブマッチがあるんですが、これを解こうとしていてバグを見つけました。
(マッチしなかった名前付きサブマッチを問い合わせると#fではなく""が返ってしまう)

というわけで普通にregexpを使った回答です。
1
2
3
4
5
6
7
(define (pick string)
  (cond
   ((#/([a-zA-Z]+)(-hidden)?(?:-(small|big))?\.([a-zA-Z]+)/ string)
    => (lambda (m)
         (print "name: '"(m 1)"', ext: '"(m 4)"', size: "(or (m 3) "normal")
                ", hidden: "(if (m 2)"True" "False"))
         (pick (m'after))))))
すみません、名前を付けた正規表現を知らないので、とりあえず普通の正規表現で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import re

def extract(s):
    for m in re.finditer('([a-zA-Z]+)(-hidden)?(?:-(big|small))?\.([a-zA-Z]+)', s):
        print "name:%r, ext:%r, size: %s hidden: %r" % \
            (m.group(1), m.group(4), m.group(3) or "normal", bool(m.group(2)))

def main():
    extract("""\
aaa abc-hidden.png>hoge-big.jpeg
---foo-hidden-small.gif|^_^a.bmp
--hiddena-hoge.png<=not hidden~~
--small.jpg<=not small(^_^)
normal-small-big.hoge""")

if __name__ == '__main__':
    main()
名前つき正規表現なら、大体こんな感じになると思いますが、
このお題の「-hidden」の処理のように、なんらかの操作が
必要な場合は、ありがたみが少々落ちますね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import re

dat = '''aaa abc-hidden.png>hoge-big.jpeg
---foo-hidden-small.gif|^_^a.bmp
--hiddena-hoge.png<=not hidden~~
--small.jpg<=not small(^_^)
normal-small-big.hoge
'''
ptn = '(?P<name>[a-zA-Z]+)(-hidden)?(?:-(?P<size>big|small))?\.(?P<ext>[a-zA-Z]+)'
fmt = "name:'%(name)s', ext:'%(ext)s', size: %(size)s, hidden: %%s"

for g in re.finditer(ptn, dat):
  print fmt % g.groupdict('normal') % bool(g.group(2))
それは%の文字列フォーマット操作で全部やってしまおうとせずに
素直にif文を使う方が後々読みやすくていいと思います。
1
2
3
4
5
6
7
8
ptn = '(?P<name>[a-zA-Z]+)(?P<hidden>-hidden)?(?:-(?P<size>big|small))?\.(?P<ext>[a-zA-Z]+)'
fmt = "name:'%(name)s', ext:'%(ext)s', size: %(size)s, hidden: %(hidden)s"

for g in re.finditer(ptn, dat):
	params = g.groupdict()
	params["hidden"] = (params["hidden"] == "-hidden")
	if not params["size"]: params["size"] = "normal"
	print fmt % params
やはり一番乗りは無理ですね……

出力結果を示しておきます。
name:'abc', ext:'png', size: normal, hidden true
name:'hoge', ext:'jpeg', size: big, hidden false
name:'foo', ext:'gif', size: small, hidden true
name:'a', ext:'bmp', size: normal, hidden false
name:'hiddena', ext:'png', size: normal, hidden false
name:'small', ext:'jpg', size: normal, hidden false
name:'normal', ext:'hoge', size: big, hidden false

#5行目がサンプル出力と異なるのはサンプル入力の問題と思われます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.regex.*;
import java.util.*;

public class Sample {
    private static final Pattern FILE_NAME_PATTERN = Pattern.
        compile("¥¥b(¥¥p{Alpha}+)((¥¥-¥¥p{Alpha}+)*)¥¥.(¥¥p{Alpha}+)¥¥b");
    public static void filter(String in) {
        Matcher m = FILE_NAME_PATTERN.matcher(in);
        while (m.find()) {
            Set<String> attrSet= new HashSet<String>(Arrays.asList(m.group(2).
                                                            split("¥¥-")));
            String size = "normal";
            if (attrSet.contains("big")) {
                size = "big";
            } else if (attrSet.contains("small")) {
                size = "small";
            }
            System.out.printf("name:'%s', ext:'%s', size: %s, hidden %b%n",
                m.group(1), m.group(4), size, attrSet.contains("hidden"));
        }
    }

    public static void main(String[] args) {
        filter("aaa abc-hidden.png>hoge-big.jpeg¥n" +
               "---foo-hidden-small.gif|^_^a.bmp¥n" +
               "--hiddena-hoge.png<=not hidden‾‾¥n" +
               "--small.jpg<=not small(^_^)¥n" +
               "normal-small-big.hoge¥n");
    }
}
いや、「特別な意味の-hidden, -small, -big以外でハイフンが使われることはない」から、5番目の出力は "hoge.png" で切れると解釈されると思います。
どうやら、問題の解釈を間違っていたようです。失礼しました。
題意に合うように訂正しました。
1
2
3
4
5
6
7
diff 2522.java Sample.java
5,6c5,6
<     private static final Pattern FILE_NAME_PATTERN = Pattern.
<       compile("¥¥b(¥¥p{Alpha}+)((¥¥-¥¥p{Alpha}+)*)¥¥.(¥¥p{Alpha}+)¥¥b");
---
>     private static final Pattern FILE_NAME_PATTERN = Pattern.compile(
>         "¥¥b(¥¥p{Alpha}+)(((?:-hidden)|(?:-small)|(?:-big))*)¥¥.(¥¥p{Alpha}+)¥¥b");
これだと7番目もうまくいかないのでは? (smallとbigが同時に出現しない、との制約から nameがsmallである、と解釈される)
その通りですね。訂正しました(今度こそ大丈夫でしょうか)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import java.util.regex.*;

public class Sample {
    private static final Pattern FILE_NAME_PATTERN = Pattern.compile(
        "¥¥b(¥¥p{Alpha}+)(-hidden)?(-small|-big)?¥¥.(¥¥p{Alpha}+)¥¥b");
    public static void filter(String in) {
        Matcher m = FILE_NAME_PATTERN.matcher(in);
        while (m.find()) {
            String size = "normal";
            if ("-big".equals(m.group(3))) {
                size = "big";
            } else if ("-small".equals(m.group(3))) {
                size = "small";
            }
            System.out.printf("name:'%s', ext:'%s', size: %s, hidden %b%n",
                m.group(1), m.group(4), size, "-hidden".equals(m.group(2)));
        }
    }

    public static void main(String[] args) {
        filter("aaa abc-hidden.png>hoge-big.jpeg¥n" +
               "---foo-hidden-small.gif|^_^a.bmp¥n" +
               "--hiddena-hoge.png<=not hidden‾‾¥n" +
               "--small.jpg<=not small(^_^)¥n" +
               "normal-small-big.hoge¥n");
    }
}
scanは強力無比
1
2
3
4
5
6
def print_image_spec(str)
  str.scan(/([A-Za-z]+)(-hidden)?(?:-(small|big))?\.([A-Za-z]+)/) do |name, hidden, size, ext|
    printf("name:'%s', ext:'%s', size: %s hidden: %s\n",
      name, ext, size||'normal', hidden ? 'True' : 'False')
  end
end
 クロージャで名前を(付けた気分に)。

javascript:alert((function($,r){r=[];$.replace(/([a-z]+)(-hidden)?(?:-(big|small))?\.([a-z]+)/gi,function(_,n,h,s,e){r.push(['name:'+n,'ext:'+e,'size:'+(s||'normal'),'hidden:'+!!h].join(', '))});return r.join('\n')})(document.body.innerHTML))
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function doukaku51($){
  var r = [];
  $.replace(/([a-z]+)(-hidden)?(?:-(big|small))?\.([a-z]+)/gi, function(_, n, h, s, e){
    r[r.length] = { name: n, ext: e, size: s || 'normal', hidden: !!h };
  });
  return r;
}
(this.WSH ? function($){WSH.echo($)} : alert)((function(a){
  for(var i = a.length, k, t; t = [], i--;){
    for(k in a[i]) t[t.length] = k +': '+ a[i][k];
    a[i] = t.join(',  ');
  }
  return a.join('\n');
})(doukaku51('aaa abc-hidden.png>hoge-big.jpeg\n'+
             '---foo-hidden-small.gif|^_^a.bmp\n'+
             '--hiddena-hoge.png<=not hidden~~\n'+
             '--small.jpg<=not small(^_^)\n'+
             'normal-small-big.hoge')));
きっともっといい方法あるだろう
1
2
3
4
5
6
7
8
sub print_image_spec {
    $_ = $_[0];
    while(my($name, $hidden, $size, $ext, $rest) = /([A-Za-z]+)(-hidden)?(?:-(small|big))?\.([A-Za-z]+)/) {
        printf("name:'%s', ext:'%s', size: %s hidden: %s\n",
               $name, $ext, $size||'normal', $hidden ? 'True' : 'False');
        $_ = $';
    }
}

	
1
2
3
4
5
6
7
8
9
(defun print-image-spec (str)
  (ppcre:regex-replace-all "([A-Za-z]+)(-hidden)?(?:-(small|big))?\\.([A-Za-z]+)" str
                           (lambda (m name hidden size ext)
                                   (format t "name:'~a', ext:'~a', size: ~a hidden: ~a~%"
                                           name ext
                                           (if (zerop (length size)) "normal" size)
                                           (if hidden "True" "False"))
                                   m)
                           :simple-calls t))
カレントバッファを入力としてM-x print-image-spec-on-bufferすると*image-spec*バッファに出力されます。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(require 'cl)
(defun print-image-spec-on-buffer ()
  (interactive)
  (save-excursion
    (goto-char (point-min))
    (with-output-to-temp-buffer "*image-spec*"
      (while (re-search-forward "\\([A-Za-z]+\\)\\(-hidden\\)?\\(-\\(small\\|big\\)\\)?\\.\\([A-Za-z]+\\)" nil t)
        (destructuring-bind (s0 e0  sname ename  shidden ehidden _ _ ssize esize  sext eext)
            (match-data)
         (princ (format "name:'%s', ext:'%s', size: %s hidden: %s\n"
                        (buffer-substring sname ename)
                        (buffer-substring sext eext)
                        (if ssize (buffer-substring ssize esize) "normal")
                        (if shidden "True" "False"))))))))

Stringを拡張してます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class ExtendedString(self:String) {
  import java.util.regex._
  def regexEach[A](reg:String)(f:(Matcher)=>A) = {
    val m = Pattern.compile(reg).matcher(self)
    while(m.find) {f(m)}
  }
}
implicit def string2ext(self:String) = new ExtendedString(self);

"""
aaa abc-hidden.png>hoge-big.jpeg
---foo-hidden-small.gif|^_^a.bmp
--hiddena-hoge.png<=not hidden~~
--small.jpg<=not small(^_^)
normal-small-big.hoge
""".trim.regexEach("([a-zA-Z]+)(-hidden)?(?:-(small|big))?\\.([a-zA-Z]+)"){ m =>
  println("name: '"+m.group(1)+"', ext: '"+m.group(4)+
          "', size: "+(if(m.group(3)==null){"normal"}else{m.group(3)}) + 
          ", hidden: "+(if(m.group(2)==null){"False"}else{"True"}))
}
良く分かりませんでした。
取り合えずparsecの練習で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module Main where

import System
import Char
import Text.ParserCombinators.Parsec hiding (spaces)
import Monad
import Control.Monad.Error
import IO hiding (try)

main = readFile "a.txt" >>= pp >>= print >> return ()
pp s = case parse (many $try(wordx)<|>other) "lisp" s of
         (Right x) -> return $filter okparse x
         (Left x)  -> error "error"
okparse ("",_,_,_) = False
okparse  (_,_,_,_)= True
w = many1 letter
hi = do  _ <- char '-'
         x <- string "hidden"
         return x
sz = do  _ <- char '-'
         x <- try(string "big") <|> string "small"
         return x

wordx = do x <- w
           (y, z) <- (do yy <-try (hi)
                         zz <-option "" sz
                         return (yy,zz))
                     <|>
                     (do zz<- option "" sz
                         return ("", zz))
           char '.'
           ext <- w
           return (x,y,z, ext)

other = do x <- noneOf ""
           return ("", "","","")
個人的はProgramming Policyにより、正規表現は封印中
でもParsec は使い方を思い出すのがちょっと面倒ね。
ということで。。。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import Data.Char
import Text.ParserCombinators.ReadP

type Name = String
type Size = String
type Hidden = Bool

type Info = (Name,Name,Size,Hidden)

pName :: ReadP Name
pName = munch1 isAlpha

pSize :: ReadP Size
pSize = (string "-big"   >> return "big")
    +++ (string "-small" >> return "small")
    +++ (return "normal")

pHidden :: ReadP Hidden
pHidden = (string "-hidden" >> return True)
      +++ (return False)

pExt :: ReadP Name
pExt =  char '.' >> pName

pInfo :: ReadP Info 
pInfo = do { name <- pName
	   ; size <- pSize
	   ; hidden <- pHidden
	   ; ext <- pExt
	   ; return (name,ext,size,hidden)
	   }

showInfo :: Info -> String
showInfo (name,ext,size,hidden)
 = "name:'"++name++"', ext:'"++ext++", size: "++size++" hidden: "++show hidden

readInfo :: ReadS Info
readInfo = readP_to_S pInfo

infoReader :: String -> [Info]
infoReader [] = []
infoReader ccs@(c:cs) = case readInfo ccs of
  []          -> infoReader cs
  (info,rs):_ -> info : infoReader rs

main :: IO ()
main = test

test :: IO ()
test = putStr $ unlines $ map showInfo $ infoReader $ testdata

testdata = unlines
 ["aaa abc-hidden.png>hoge-big.jpeg"
 ,"---foo-hidden-small.gif|^_^a.bmp"
 ,"--hiddena-hoge.png<=not hidden~~"
 ,"--small.jpg<=not small(^_^)"
 ,"normal-small-big.hoge"]

{- 実行例
*Main> :main
name:'abc', ext:'png, size: normal hidden: True
name:'hoge', ext:'jpeg, size: big hidden: False
name:'hidden', ext:'gif, size: small hidden: False
name:'a', ext:'bmp, size: normal hidden: False
name:'hoge', ext:'png, size: normal hidden: False
name:'small', ext:'jpg, size: normal hidden: False
name:'small', ext:'hoge, size: big hidden: False
-}
書きわすれましたが、
Text.ParserCombinators.ReadP モジュールを
つかっています。Parsecほど細かい情報を扱えませんが、
その分、お気軽に使えます。
文字列からパターンにマッチする表現のデータをとりだしたり,
特定のパターンにマッチする表現を別の表現で置換したり,

というのが正規表現の使いどころなんだね.
(という自明なことに今さら気づきました.^^;)

とするとパターンを指定するのは正規表現ではなくて,たとえばもうすこし
拡張してパーザで指定してもいいわけですよね.(これも自明か^^;)

たとえば,ReadP a が一般化されたパターンを表すと思えばいいわけでよね...

そうすると性能の問題はあるにせよ、
一般化したパターンをもらって,(雑音のある)文字列からデータを取り出す関数を
生成する関数 reader とか,パターン(とデータを文字列に変換する関数)をもらって
文字列のなかの部分列を置換する関数を生成する関数 replacer とかがあれば汎用性
があるはずですよねぇ.(正規表現はまさにこのように使うのでしょうから)

そうすると先に投稿したコードでは infoReader の定義は

infoReader = reader pInfo

となるし,たとえば入力文字列からデータ部分をとりのぞいて雑音部分だけに
してしまうにような infoNoise は

infoNoise = replacer pInfo (const "") 

なんてことができますね.

*Main> let infoNoise = replacer pInfo (const "")
*Main> infoNoise testdata
"aaa >\n---foo-|^_^\n--hiddena-<=not hidden~~\n--<=not small(^_^)\nnormal-\n"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
reader :: ReadP a -> (String -> [a])
reader parser string = case string of
 "" -> []
 _  -> case readP_to_S parser string of
   []            -> reader parser (tail string)
   (a,string'):_ -> a : reader parser string'

replacer :: ReadP a -> (a -> String) -> String -> String
replacer p s str = case str of
  "" -> ""
  _  -> case readP_to_S p str of
    []         -> head str : replacer p s (tail str)
    (a,str'):_ -> s a ++ replacer p s str'
ごめんなさい。プログラミング初心者です。
初めてcl-ppcre使ってみました。
勉強します。精進します。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
(defun frob (str)
  (let ((l (remove-if-not (lambda (s) (cl-ppcre:scan "\\." s)) (cl-ppcre:split "([^A-z-\\.]|_|\\^)" str))))
    (dolist (item l)
      (destructuring-bind (name prop _ ext)
	  (map 'list #'values
	       (nth-value 1 (cl-ppcre:scan-to-strings "[^A-z]*([A-z]*)((-small|-big|-hidden)*)\\.([A-z]*)" item)))
	(declare (ignore _))
	(format t "name: '~A',~,8@Text: '~A', ~,8@Tsize: ~[Small~;Normal~;Big~], ~,8@THidden: ~:[False~;True~]~%" 
		name ext (big-or-small-or-nomal-p prop) (hiddenp prop))))))

(defun hiddenp (str)
  (if (cl-ppcre:scan "-hidden[\\.-]*" str) t nil))

(defun big-or-small-or-nomal-p (str)
  (cond ((cl-ppcre:scan "-big" str) 2) 
	((cl-ppcre:scan "-small" str) 0)
	('T 1)))
正規表現使わないと結構面倒ですね。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
#include <iostream>
#include <string>
#include <algorithm>
#include <functional>
#include <iterator>
#include <ctype.h> // isalpha()

struct is_alpha : public std::unary_function<char, bool>
{
    bool operator()(char c) const
    {
        return isalpha(c);
    }
};

bool option(
    std::string::const_iterator beg,
    std::string::const_iterator& it,
    const std::string& name,
    std::string& ret)
{
    const std::string s = "-" + name;

    if (std::distance(beg, it) >= s.size() && std::equal(it - s.size(), it, s.begin()))
    {
        it -= s.size();

        ret = name;

        return true;
    }
    else
    {
        return false;
    }
}

void extract(const std::string& s)
{
    std::string::const_iterator it = s.begin();

    while ((it = std::find(it, s.end(), '.')) != s.end())
    {
        std::string::const_iterator it2;

        it2 = std::find_if(it + 1, s.end(), std::not1(is_alpha()));

        const std::string ext(it + 1, it2);

        std::string size, hidden;

        it2 = it;

        option(s.begin(), it2, "big", size) || option(s.begin(), it2, "small", size);

        option(s.begin(), it2, "hidden", hidden);

        std::string name(
            std::find_if(
                std::string::const_reverse_iterator(it2),
                s.rend(),
                std::not1(is_alpha())
            ).base(), it2
        );

        if (name.empty())
        {
            if (!hidden.empty())
            {
                name.swap(hidden);
            }
            else if (!size.empty())
            {
                name.swap(size);
            }
        }

        if (size.empty())
        {
            size = "normal";
        }

        std::cout
            << "name:'" << name << "', "
            << "ext:'" << ext << "', "
            << "size: " << size << " "
            << "hidden: " << (!hidden.empty())
            << std::endl;

        ++it;
    }
}

int main()
{
    std::boolalpha(std::cout);

    extract(
        "aaa abc-hidden.png>hoge-big.jpeg\n"
        "---foo-hidden-small.gif|^_^a.bmp\n"
        "--hiddena-hoge.png<=not hidden~~\n"
        "--small.jpg<=not small(^_^)\n"
        "normal-small-big.hoge\n"
    );
}
今までPerl互換の方の正規表現関数は使わなかったのだけれど
繰り返し検索はこっちにしかなかったので使ってみた。
…これからもっと使おう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
$str='aaa abc-hidden.png>hoge-big.jpeg
---foo-hidden-small.gif|^_^a.bmp
--hiddena-hoge.png<=not hidden~~
--small.jpg<=not small(^_^)
normal-small-big.hoge';

$matches=array();
preg_match_all('/([a-zA-Z]+)(-hidden|)(-(small|big)|)\.([a-zA-Z]+)/', $str, $matches, PREG_SET_ORDER );

while(list(,$v)=each($matches))
{	$hd=$v[2]?"True":"False";
	$sz=$v[4]?$v[4]:"normal";
	echo "name:'${v[1]}', ext:'${v[5]}', size: $sz hidden: $hd\n";
}
?>
11行目は
foreach($matches as $v)
のほうが簡単では?
まぁそこは趣味の問題ということで…。
($vに代入する表記がないのに値が変わっていくのが嫌とか
 'as'って表記がphpの文法から外れてる気がして嫌とか
 思ってしまうのは歳のせいなんでしょうかね。
 perl互換正規表現と同じで食わず嫌いはなくさないといけませんね…。
 そういえば'&&','||'と同じ意味で優先順位の違う'and'や'or'っていう
 演算子もあるんですね…。)
Squeak の Smalltalk で。正規表現は使えないので、手続き的に。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
| in delimiters candidates |
in := 'aaa abc-hidden.png>hoge-big.jpeg
---foo-hidden-small.gif|^_^a.bmp
--hiddena-hoge.png<=not hidden~~
--small.jpg<=not small(^_^)
normal-small-big.hoge'.

delimiters := in asSet reject: [:each | each isLetter or: ['-.' includes: each]].
candidates := (in findTokens: delimiters) reject: [:each | each = each sansPeriodSuffix].
World findATranscript: nil.
candidates do: [:cand |
	| strm next ext size hidden |
	strm := (cand findTokens: '-.') reversed readStream.
	ext := strm next.
	next := strm next.
	(strm peek notNil and: [#(small big) includes: next])
		ifTrue: [size := next. next := strm next] ifFalse: [size := 'normal'].
	(hidden := strm peek notNil and: [next = #hidden])
		ifTrue: [next := strm next].
	Transcript cr; show: {#name->next. #ext->ext. #size->size. #hidden->hidden}]
Perl 5.10(開発版はPerl 5.9.5)から、named capture が使えるようになります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sub print_image_spec {
    my $str = shift;
    while ( $str =~ /(?<name>[A-Za-z]+)(?<hidden>-hidden)?(?:-(?<size>small|big))?\.(?<ext>[A-Za-z]+)/g ) {
        printf(
            "name:'%s', ext:'%s', size: %s hidden: %s\n",
            $+{name}, $+{ext},
            $+{size} || 'normal',
            $+{hidden} ? 'True' : 'False'
        );
    }
}
とりあえず動くコードをup。
最近のgawkだともっと効率の良い書き方ができるような気がします。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
	s = $0
	while (match(s,/[a-z]+(-hidden)?(-small|-big)?\.[a-z]+/)) {
		matched = substr(s,RSTART,RLENGTH)
		s = substr(s,RSTART+RLENGTH+1)

		hyphen = index(matched,"-")
		period = index(matched,".")

		if (hyphen > 0) {
			name = substr(matched, 1, hyphen-1)
			extra = substr(matched, hyphen, period - hyphen)
			hidden = (extra ~ /-hidden/) ? "True" : "False"
			if (extra ~ /-small/) size = "small"
			else if (extra ~ /-big/) size = "big"
			else size = "normal"
			ext = substr(matched, period+1)
		} else {
			name = substr(matched, 1, period-1)
			hidden = "False"
			size = "normal"
			ext = substr(matched, period+1)
		}
		printf("name:'%s', ext:'%s', size: %s hidden: %s\n", name, ext, size, hidden)
	}
}