第24回◯◯o◯裏番組シェル芸勉強会に参加させて頂きました!

目からうろこが滝のように落ちました。
kanata約8年前に追加

第24回◯◯o◯裏番組シェル芸勉強会に参加させて頂きました。

シェルスクリプト大好きなんですが、今回は新しい知見が得られすぎて、目からうろこが滝のように落ちました。
問題解説は、本家のサイトが詳しいので、そちらにお任せするとして、ここは私なりの感想と解説(?)をしたいと思います。

【問題と解答】第24回◯◯o◯裏番組シェル芸勉強会

第6回もう初心者向けでないなんて言わないよ絶対午前のシェル勉強会/第24回◯◯o◯裏番組シェル芸勉強会

Q1

$ cat Q1
玉子 卵 玉子 玉子 玉子 玉子
玉子 玉子 卵 卵 卵 玉子
卵 玉子 卵 玉子 玉子 玉子
卵 玉子 卵 卵 卵 卵
玉子 卵 玉子

上のようなQ1ファイルについて、次のような出力を得てください。

玉子:5 卵:1 
玉子:3 卵:3 
玉子:4 卵:2 
玉子:1 卵:5 
玉子:2 卵:1 

解答&解説

現地では、時間切れ。解説を聞いてなるほど~。と思った。むしろawk芸。

$ cat Q1 | awk '{for(i=1;i<=NF;i++){a[$i]++};for(k in a){printf("%s:%d ",k,a[k]);a[k]=0}print ""}'

私なりに解説しますと、awkの所がこんな感じ。

{
  for(i=1;i<=NF;i++){
    a[$i]++                  # 連想配列を使って、玉子と卵をカウント
  };
  for(k in a){
    printf("%s:%d ",k,a[k]); # 1行分の玉子と卵を出力
    a[k]=0                   # 連想配列を初期化
  }
  print ""                   # 改行を出力
}

Q2

次のようなテキストについて、繰り返し出てきた文字の2つ目以降を省いて出力してください。例えばQ2のファイル

$ cat Q2
へのへのもへじ

の場合、「へのもじ」が正解の出力になります。

解答&解説

自分が作ったシェル芸と解答が同じだった

$ cat Q2 | grep -o . | awk '{if(!a[$1]){printf $1};a[$1]=1}END{print ""}'

横文字を縦にする技

$ echo abcdefg | grep -o .
a
b
c
d
e
f
g
$ echo abcdefg | fold -w 1  # これでもいいか
a
b
c
d
e
f
g
$ echo abcdefg | sed 's/./&\n/g' # まぁ、これでもいいか
a
b
c
d
e
f
g

awk部分の解説

{
  if(!a[$1]){ # 連想配列の要素が存在していなかったら
    printf $1 # 文字を表示する
  };
  a[$1]=1     # 連想配列を存在させる
}
END{
  print ""    # 最後の最後に改行する
}

Q3

$ cat Q3
金 日成
キム ワイプ
金 正日
キム タオル
金 正男

というデータを、

%%
キム タオル
キム ワイプ
%%
金 正男
金 正日
金 日成
%%

というように第一フィールドをキーにして%%でレコードを区切ってください。awkを使ってできた人は、awkを使わないでやってみてください。

解答&解説

$ sort Q3 | awk '{if($1!=a){print "%%";print;a=$1}else{print}}END{print "%%"}'

awk解説

{
  if($1!=a){    # 1個目の要素(キムか金)が、aじゃなかったら
    print "%%"; # %%を出力
    print;      # 1行出力 
    a=$1        # aに1個目の要素(キムか金)を代入
  }else{
    print       # 1行出力
  }
}
END{
  print "%%"    # 最後の%%を出力
}

なるほどね~

Q4

Q4.xlsxのA1のセルには数字が書いてあります。その数字を出力してください。A4には文字列が書いてあるので余裕がある人はそれも特定してみましょう。

解答&解説

拡張子xlsxのファイルは実はzipという事を知っていれば、なんとかなる問題

A1のセル

初めて使ったけど、zipgrepというコマンドを使った。上田先生の解答はunzip -p を使っていました。

$ zipgrep . Q4.xlsx |grep A1
caution: filename not matched:  [Content_Types].xml
xl/worksheets/sheet1.xml:<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="x14ac" xmlns:x14ac="http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"><dimension ref="A1:B4"/><sheetViews><sheetView tabSelected="1" workbookViewId="0"><selection activeCell="A5" sqref="A5"/></sheetView></sheetViews><sheetFormatPr baseColWidth="12" defaultRowHeight="18" x14ac:dyDescent="0"/><cols><col min="1" max="1" width="15.83203125" bestFit="1" customWidth="1"/><col min="2" max="2" width="14.33203125" bestFit="1" customWidth="1"/></cols><sheetData><row r="1" spans="1:2"><c r="A1"><v>114514</v></c><c r="B1" t="s"><v>0</v></c></row><row r="2" spans="1:2"><c r="A2" t="s"><v>1</v></c><c r="B2" t="s"><v>3</v></c></row><row r="3" spans="1:2"><c r="A3" t="s"><v>2</v></c><c r="B3" t="s"><v>4</v></c></row><row r="4" spans="1:2"><c r="A4" t="s"><v>6</v></c><c r="B4" t="s"><v>5</v></c></row></sheetData><phoneticPr fontId="1"/><pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/><extLst><ext uri="{64002731-A6B0-56B0-2670-7721B7C09600}" xmlns:mx="http://schemas.microsoft.com/office/mac/excel/2008/main"><mx:PLV Mode="0" OnePage="0" WScale="0"/></ext></extLst></worksheet>

これで答えが見えるので、強引になんとかできる。

$ zipgrep . Q4.xlsx |sed 's/<c/\n<c/g'|grep r=\"A1|cut -d '>' -f 3 |cut -d '<' -f 1
114514

A4のセル

上田先生のエクシェル芸ツールを使ってズルしたw

$ exread-str Q4.xlsx 
0 シェル芸バイブ
1 危険シェル芸
2 キュアエンジニア
3 ドラゴン曲線
4 素数
5 変態シェル芸
6 エクシェル芸

A4のセルに入っている文字列が、6番めの"エクシェル芸"であるということは、以下でわかる

$  zipgrep . Q4.xlsx |sed 's/<c/\n<c/g'|grep A4
caution: filename not matched:  [Content_Types].xml
<c r="A4" t="s"><v>6</v></c>

Q5

ファイルQ5について、xに好きな数を代入して各行の式を計算してください。

$ cat Q5
x + x^2
x + 1/x
x*x*x

余裕のある人は、例えばxに2を代入したければ、

$ echo 2 | ...

というようにecho <代入したい数>から始めてワンライナーで解いてみてください。

解答&解説

解説を聞いて、はえ~ってなった。

$ echo -2 | xargs -I@ awk -v a=@ '{gsub(/x/,a,$0);print}' Q5 | bc -l

awkは、こんな感じ

# a変数には、-2が設定されているところからスタート
{
  gsub(/x/,a,$0); # xを-2で置換 $0は1行全体が置換対象という意味
  print           # 出力
}

Q6

「玉子」と「卵」の数を数えて、数が少ない方を数が大きい方で置換してください。

$ cat Q6 
卵卵玉子玉子玉子玉子玉子卵卵卵玉子玉子卵玉子玉子玉子玉子卵卵玉子卵玉子卵卵玉子卵玉子

解答&解説

力技でいかようにもできそうだけれど、エレガントな解法を考えるとキツイ
解答もやっぱり力技だったw

$ cat Q6 | grep -oE '(玉子|卵)' | sort | uniq -c | sort -n -k1,1n | awk '{print $2}' | xargs | awk '{print "s/"$1"/"$2"/g"}' | xargs -I@ sed @ Q6

grep -oE '(玉子|卵)' は、縦にする魔法。mecabを使うという方法もあったが、私の環境には入っていない。

$ cat Q6 | grep -oE '(玉子|卵)' 
卵
卵
玉子
玉子
玉子
玉子
玉子
卵
卵
卵
玉子
玉子
卵
玉子
玉子
玉子
玉子
卵
卵
玉子
卵
玉子
卵
卵
玉子
卵
玉子

カウントすると玉子の方が多いですね

$ cat Q6 | grep -oE '(玉子|卵)' | sort | uniq -c | sort -n -k1,1n 
     12 卵
     15 玉子

次が新しい知見で、awkでなんとsedのパラメタを作り出して流しこむという・・・!!

これが

$ cat Q6 | grep -oE '(玉子|卵)' | sort | uniq -c | sort -n -k1,1n | awk '{print $2}' | xargs | awk '{print "s/"$1"/"$2"/g"}' 
s/卵/玉子/g

こうなる・・・!

$ cat Q6 | grep -oE '(玉子|卵)' | sort | uniq -c | sort -n -k1,1n | awk '{print $2}' | xargs | awk '{print "s/"$1"/"$2"/g"}' | xargs -I@ sed @ Q6
玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子玉子

Q7

次のseq(あるいはjot等)の出力から、各桁の数字の構成が同じもの(例: 11122と22111等)を重複とみなし、除去してください。

$ seq -w 00000 99999

解答&解説

考え方は合ってたんだけど、コードが書けなかった。
考え方としては、1行の各数字をソートして、uniqすればいい(上田先生の解答では、 sort -u を使っている)
awkのasort関数か~。そういうのがあるのか~。

$ seq -w 00000 99999 | sed 's/./& /g' | awk '{for(i=1;i<=NF;i++)a[i]=$i;asort(a);for(k in a){printf a[k]}print ""}' | sort -u

空白区切りでバラす技

$ echo abcdefg | sed 's/./& /g'
a b c d e f g 

awkは、こんな感じ

{
  for(i=1;i<=NF;i++)a[i]=$i; # 連想配列に数字を格納
  asort(a);                  # 連想配列aをソート
  for(k in a){               # 
    printf a[k]              # そして表示
  }
  print ""                   # 改行を出力
}

Q8

  1. まず、1〜7を全て含む7桁の整数を全て列挙して、tmpというファイルに出力してください。

  2. 次に、相異なる7以下の正の整数a,b,c,d,e,f,gを用いて、

abcd + efg

と表せる素数と、その時のa〜gの数字を全て求めましょう。tmpを用いて構いません。

(参考: 2011年日本数学オリンピック予選第3問から。一部改。http://www.imojp.org/challenge/old/jmo21yq.html)

解答&解説

まず、1〜7を全て含む7桁の整数を全て列挙して、tmpというファイルに出力してください。

ズルしたw
自作の重複順列作成ツールがあるので、それを使ってしまった。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# 重複順列求めるツール ver 0.1
# 2016.08.02 kanata

import sys
from itertools import permutations

if( (len(sys.argv)-1) != 2 or not sys.argv[1].isdigit() ):
  print 'Usage:'+sys.argv[0]+" [length] [element]"
  print '   ex:'+sys.argv[0]+" 3 abc123"
  sys.exit(1)

# permutations…重複順列
# combinations…組み合わせ
itr = permutations(sys.argv[2],int(sys.argv[1]))

for i in itr:
  print "".join(map(str,i))
sys.exit(0)
$ permutations.py 7 1234567
1234567
1234576
1234657
  .
  .
7654321

ただ、上田先生の解法が目から鱗で、1234567の全てを含む結果を出せばいいだけだった。

$ seq -w 0000000 9999999 | grep -v [089] | grep 1 | grep 2 | grep 3 | grep 4 | grep 5 | grep 6 | grep 7 > tmp

次に、相異なる7以下の正の整数a,b,c,d,e,f,gを用いて...

factorコマンドを使うというイメージはあったんだけど、そこで止まった。

$ cat tmp | sed 's/./& /g' | awk '{print $1$2$3$4$5$6$7,$1*$2*$3*$4+$5*$6*$7}' | while read a b ; do echo $b | factor | awk -v n=$a 'NF==2{gsub(/./,"& ",n);print n,$2}' ; done

まず、[元の数字列] [計算結果]というフォーマットを出力する

$ cat tmp | sed 's/./& /g' | awk '{print $1$2$3$4$5$6$7,$1*$2*$3*$4+$5*$6*$7}' 
1234567 234
1234576 234
1234657 234
     .
     .

これに対してwhile文を使う

while read a b
do
  echo $b | factor | awk -v n=$a 'NF==2{gsub(/./,"& ",n);print n,$2}'
done

$bが素数であるという事と、それを表示するという事を以下でやってる。

$ echo 179 | factor | awk -v n=$a 'NF==2{print $2}' # 素数じゃなかったら出力されない
179

いい感じで整形した結果

$ cat tmp | sed 's/./& /g' | awk '{print $1$2$3$4$5$6$7,$1*$2*$3*$4+$5*$6*$7}' | while read a b ; do echo $b | factor | awk -v n=$a 'NF==2{gsub(/./,"& ",n);print n,$2}' ; done
2 3 4 6 1 5 7  179
2 3 4 6 1 7 5  179
2 3 4 6 5 1 7  179
        .
        .
        .

勉強になりました~。


コメント

クリップボードから画像を追加 (サイズの上限: 100 MB)