tech.guitarrapc.cóm

Technical updates

PowerShellでStringをChar[]に変換する

表題の事案には、シェル芸で試みている時に巡り会っていたのです。 さてちょうど、いつもの問題出題サイトで表題の試みが行われていたようです。
Convert a string to a character array
たまたま経験則で答えが出てたのですが、改めて勉強になりました。 さっそく見てみます。

お題目

"PowerShell"というStringを、一文字ずつのChar[]に変換しなさい。
つまり、こうなるはずです。
P
o
w
e
r
S
h
e
l
l

縛り

  1. [char[]]へのキャストは禁止。
  2. String型が持つ.ToCharArrayメソッドは禁止。
ソースにすると、これが禁止です。
[char[]]"PowerShell" #禁止
("PowerShell").ToCharArray() #禁止
型を調べてみましょう。
([char[]]"PowerShell").gettype().FullName
("PowerShell").ToCharArray().gettype().FullName
結果は[Char[]]型ですね。
System.Char[]
System.Char[]

コード例

一文字ずつ取り出す方法は、いくつか考えられます。
  1. .GetEnumetrator()を利用
  2. .GetEnumetrator()を利用して順次配列にいれていく
  3. 配列(Array)にして一字ずつ取り出す
  4. 正規表現して、結果をSplitメソッドへ
  5. Splitメソッドを使う
それぞれを見ていきます。

.GetEnumetrator()を利用

まずはソースから。
$Enumerator="PowerShell".GetEnumerator()
実行してみましょう。
"------Enumerator"
$Enumerator.length
$Enumerator.GetType().Name
"------Enumerator"
$Enumerator
"------Enumerator"
結果です。 GetEnumerator()では、型がCharEnumeratorになっていることが分かります。 あと、順次Enumeratorしてるので…結果が変数には…あれれ?何も出力されていません。
------Enumerator
1
1
1
1
1
1
1
1
1
1
CharEnumerator
------Enumerator
------Enumerator
これは正常な動きです。 仮に出力したいならば変数に入れずに実行すればいいわけです。 つまりこうですね。
"PowerShell".GetEnumerator()
期待通り出力します。
P
o
w
e
r
S
h
e
l
l

.GetEnumetrator()を利用して順次配列にいれていく

ソースです。
$EnumeratorArray=@()
$EnumeratorArray += "PowerShell".GetEnumerator()
実行してみましょう。
"------EnumeratorArray"
$EnumeratorArray.length
$EnumeratorArray.GetType().Name
"------EnumeratorArray"
$EnumeratorArray
"------EnumeratorArray"
結果です。 Charではありませんが、配列に入れているのでObjectになっていますね。 これならまぁ何とかです。 変数にも格納できています。
------EnumeratorArray
10
Object[]
------EnumeratorArray
P
o
w
e
r
S
h
e
l
l
------EnumeratorArray

配列(Array)にして一字ずつ取り出す

ソースです。 インデックスを直接書かない理由は、他の文字でも流用するためです。 毎度文字の度に変えるのはナンセンスです。
$Array=($z="Powershell")[0..$z.Length]
実行してみましょう。
"------Array"
$Array.length
$Array.GetType().Name
"------Array"
$Array
"------Array"
結果です。 object[]型ですがまぁ。一応上手くいきますね。
------Array
10
Object[]
------Array
P
o
w
e
r
s
h
e
l
l
------Array

正規表現して、結果をSplitメソッドへ

ソースです。
$regexsplit=[regex]::split("PowerShell","")
実行してみましょう。
"------regexsplit"
$regexsplit.length
$regexsplit.GetType().Name
"------regexsplit"
$regexsplit
"------regexsplit"
結果です。 突込みどころが多いしアウトです。 まず、Splitメソッドで分割すると、結果は[string]型になります。 加えて前後に余計な空白文字が1文字ずつ入って12文字になっています。
------regexsplit
12
String[]
------regexsplit

P
o
w
e
r
S
h
e
l
l

------regexsplit

Splitメソッドを使う

ソースです。
$split='PowerShell'-split'\B'
実行してみましょう。
"------split"
$split.length
$split.GetType().Name
"------split"
$split
"------split"
結果です。 先ほど同様の突込みでアウトです。 当然、Splitメソッドで分割すると結果は[string]型になります。 前後の余白が入らないだけマシですが。
------split
10
String[]
------split
P
o
w
e
r
S
h
e
l
l
------split

まとめ

以上から、[char]型で取得するには、明示的な型変換が必要です。 配列に「.GetEnumerator() 」でいれるか「$x[インデックス指定]」で入れることで[object]型は取得できます。 Splitは[string]になりますので注意です。 ありがたいことに、公開して10分で、某星人から指摘がありました。 今回の例でいうとソースはこうです。
$linq =[System.Linq.Enumerable]::ToArray("PowerShell")
実行してみます。
"------linq"
$linq.length
$linq.GetType().FullName
"------linq"
$linq
"------linq"
完璧な結果に流石の一言です。
------linq
10
System.Char[]
------linq
P
o
w
e
r
S
h
e
l
l
------linq

おまけ

lからPまで、逆さに取得したい場合は? 簡単な方法は配列のインデックスを逆にたどる方法です。 ソースはこうです。
($zz="Powershell")[-1..-($zz.Length)]
結果です。
l
l
e
h
s
r
e
w
o
P
これも星人はLinqでいくのが決まりということなのは最もでした。 ソースはこうです。
[System.Linq.Enumerable]::Reverse('PowerShell')
結果です。
l
l
e
h
s
r
e
w
o
P
トドメの一言でした。 真理です。