htsign's blog

ノンジャンルで書くつもりだけど技術系が多いんじゃないかと思います

POH7の問題でSwiftを初めて書いた

Paizaさんは相変わらず変態ですね(褒め言葉
今回のPOHも例に漏れずぶっ飛んだ設定になっています。

さて、縞ニーソの問題(この文だけですでに病気だ…)ですが、始めは無難にRubyで書いていたのですが、POH7からはSwiftコンパイラも採用されたということでSwiftでも書いてみることにしました。
ちなみにgolfもやっていたようなので、なるべく短く書くようにしました。

Swiftについては今までそこかしこでObjective-Cとの対比で「短く書ける」と紹介されていたエントリをいくつか読んだ程度で、実際に書いたことはなく、また構文も特に勉強していなかったため全く書けない状態での挑戦です。
知っていることと言ったら

var s = "variable string" // var で変数宣言
let c = "const string"    // let で定数宣言

// func で関数宣言
func f() {
  // do something
}

くらいでした。

本題

前置きはこの程度にして、設問ですが、

赤と白の等間隔の縞模様をあなたは作ろうとしています。

各色の幅 n 、縞模様全体の長さ m が改行区切りで入力されます。
赤を 'R' 、白を 'W' とした赤から始まる縞模様を出力して下さい。

例えば
3
10
のような入力の場合
RRRWWWRRRW
のように出力してください。

というものです。

1回目

最初に書いたコードはこちらです。

// 98bytes
var n=Int(readLine()!)!,m=Int(readLine()!),i=0;for;i<m;++i{print(i/n&1==0 ?"R":"W",terminator:"")}

// 展開すると
var n = Int(readLine()!)!, m = Int(readLine()!)!
for var i = 0; i < m; ++i {
  print(i / n & 1 == 0 ? "R" : "W", terminator: "")
}

軽く解説しますと、readLine()が標準入力から1行読み取る関数で、返り値の型はString?です。
String?Optional<String>と同じ意味を表し、つまりOptional型の一つです。
Optional型は型引数に取る型の値以外にnilも持てる型です。
SwiftではOptional型を純粋な型に戻す時!を後ろに付けます。
そしてInt()はキャスト用の関数です。返り値の型はなぜかInt?です。
なのでvar n = Int(readLine()!)!は標準入力から一行読み取り、読み取った文字列を数字としてnに代入する、という操作になります。

i / n & 1 == 0の部分はi / nが奇数なのか偶数なのかの判断をしています。
素直に考えれば奇数か偶数かの判断はi / n % 2 == 0、つまり「2で割って余りが0になるかどうか」になるかと思いますが、なぜこれで奇数と偶数を判断できるのか。
&演算子はビット演算子と呼ばれるものの一つで、AND演算を表します。
細かい説明は省きますが、AND演算では与えられた二つの二進数の同じ桁を比較し、両方のビットが立っている時に1とします。

0000 & 0001 = 0000 // 0 & 1 = 0
0001 & 0001 = 0001 // 1 & 1 = 1
0010 & 0001 = 0000 // 2 & 1 = 0
0011 & 0001 = 0001 // 3 & 1 = 1
0100 & 0001 = 0000 // 4 & 1 = 0
0101 & 0001 = 0001 // 5 & 1 = 1
0110 & 0001 = 0000 // 6 & 1 = 0
......

…とまぁ、こういうわけです。
別にi / n % 2 == 0でも問題ないどころか可読性はこちらの方が高いのですが、ビット演算の方がより高速に処理できます*1

始めはn回ごとに'R'と'W'を切り替える処理をゴリゴリ書いていたのですが、

i = 0
s = "R"
m.times do
  if i == n then
    s = s == "R" ? "W" : "R"
    i = 0
  end
  print s
  i += 1
end

一度このコードで提出してから、整数型を整数型で割ると返ってくる値は小数点以下を切り捨てた整数であることに気づきました。
n回ごとに'R'と'W'が切り替わるのであれば、n*2回ごとに1周するはず。
つまり0からmまでのループ変数iを取るとき、i / nが奇数なのか偶数なのかで'R'を出力するのか'W'を出力するのかが一意に決まります。
そういうわけでここでは奇数偶数判定を用いています。

さて、残るは出力ですが、Swift 1.xでは標準出力用の関数としてprintprintlnの2つを用意し、printでは末尾に改行を入れず、printlnでは末尾に改行を入れる、という差を設けていたらしいです。
しかしSwift 2.0になった時にprintlnを廃止し、printに一本化したようです。
そして改行なしの出力をしたい場合は、第二引数のオプションにterminator: ""を付けろ、ということになったみたいです。

長かったけどこれで解説とします。全然軽くなかった。

2回目

二度目に提出したのが以下です。

// 88bytes
var n=Int(readLine()!)!,s="";(0..<Int(readLine()!)!).map{s+=$0/n&1==0 ?"R":"W"};print(s)

// 展開すると
var n = Int(readLine()!)!, m = Int(readLine()!)!, s = ""
(0..<m).map {
  s += $0 / n & 1 == 0 ? "R" : "W"
}
print(s)

基本的には変わってないのですが、Rangeを利用しています。

let m = 0, n = 10

// mからnまでの範囲を表す
let r1 = m...n
// mからnまで(nを含まない)の範囲を表す
let r2 = m..<n

var a1 = [Int](), a2 = [Int]()
for var i in r1 {
  a1.append(i)
}
for var i in r2 {
  a2.append(i)
}
print(a1) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(a2) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

ちなみにforEachメソッドだと長いのでmapメソッドを使っています。
ブロック内部ではただsへの再代入をしているだけなので返り値がなく、そのことについてコンパイラが警告を出してきますが、コードを短くするためには已む無しなので無視します。

最初はmapで'R'か'W'を持つ配列にして、最後にjoinしようと考えていたのですが、

let arr = ["a", "b", "c"]
print("".join(arr)) // "abc"

とできたのは、Swift 1.xの頃の話で、Swift 2.0からは

let arr = ["a", "b", "c"]
print(arr.joinWithSeparator("")) // "abc"

となってしまい、全然コード量の節約にならなかったため、泣く泣く文字列に追加する方式を取っています。

ちなみにmapメソッド内部に書かれた$0はブロックに渡される0番目の仮引数です。

let r = 0...10

let a1 = r.map{ i in i * 2 }
let a2 = r.map{ $0 * 2 }

このa1a2は同じ結果が入ります。

こんな感じでコードを短くしていたんですが、私の頭ではこれ以上無理でした。
ランキングでは71bytesが最短らしく、私よりも17bytesも短いです。
もう少し勉強してより短い糖衣構文をいろいろ習得する必要がありそうです。


ところでRubyでは最終的に以下のようなコードを書きました。

# 48bytes
n=gets.to_i;gets.to_i.times{|i|print"RW"[i/n&1]}

# 展開すると
n = gets.to_i
m = gets.to_i

m.times do |i|
  print "RW"[i / n & 1]
end

Rubyでは文字列に添え字をつけるとsliceメソッドと等価になります。

s = "abcdefghi"

p s[2..5]        # "cdef"
p s.slice(2..5)  # "cdef"

p s[1, 2]        # "bc"
p s.slice(1, 2)  # "bc"

1をAND演算すると結果が0か1のどちらかのみに絞られることについては上で説明した通りです。
なので"RW"という2文字の0番目、または1番目を取り出して出力、というコンパクトなコードになります。

【追記】一文字減らせた

# 47bytes
n=gets.to_i;gets.to_i.times{|i|print:RW[i/n&1]}

文字列型"RW"の代わりに、シンボル型:RWを使いました。

【さらに追記】さらに一文字減らせた

# 46bytes
n,m=$<.map(&:to_i);m.times{|i|print:RW[i/n&1]}

# パーレンを省略するとさらに一文字
# 45bytes
n,m=$<.map &:to_i;m.times{|i|print:RW[i/n&1]}

Rubyの特殊変数$<を使っています。
これはARGFエイリアスで、こいつにEnumerableのメソッドを付けてやると標準入力を行ごとに取ってくるらしいです(よく理解していない)
参照: AtCoderで見かけたRubyショートコーディング術 - Qiita

あと、縞ニーソ問題のSwiftの最短コード数が69bytesになってますね(12月18日23時前現在)。変態どもめ…。

試してみたい方へ

Swiftはここで書いて遊べます。Xcode持ってるならそちらでもいいけど。
SwiftStub: Online Swift Compiler

Rubyはこちらがお勧めです。別にIdeoneでもいいんですが、こちらの方が手軽です。
compile ruby online

*1:コンパイラの最適化具合にもよります

月末ギリギリでのmineoへのMNPはやめた方がいい

mineoではMNP転入の手続きは、消費者自身がmineoマイページの所定のフォームにSIMの製造番号の下4桁を入力して完了となります。
その為、必然的にSIMが届くまではMNPを完了させることができません。
mineoで申し込みをしてから受理されるまで1~2日、さらにSIMが発送されてから届くまで2~3日かかります。

申し込んだ後にこのことに気づいて慌ててmineoサポートセンターに問い合わせても、サポートからは倉庫と連絡を取り合う手段がないため、届く前のSIMの製造番号は確認できません。
また申し込みのキャンセルもできず、SIMの受け取り拒否をしたとしても申し込みから15日後には強制的に切り替わってしまいます。

つまり月末に申し込んだ時点で、MNP元の更新月であったとしてもほぼ確実に解約違約金の10260円がかかることが確定します。
IIJmioでは申し込んだ時点で業者側が勝手に切り替えてくれていたので油断していました。*1

今回は勉強代としておとなしく支払うことにします。
くれぐれもMNPは余裕を持って行うように心がけたいです。

*1:現在ではIIJmioもmineoと同じくユーザー側で切り替える方法になっているようです。

BookLiveで今まで使ってきた金額を出すスクリプト書いた

いやー久しぶりにJavaScript書いたわー。

(function(d){var s=d.createElement("script");s.src="//dl.dropboxusercontent.com/u/414379/www/BookLivePaid/script.js";d.body.appendChild(s)})(document)

一時期Amazon.co.jpで使った額を調べるスクリプトが流行ったことがありましたが、アレのBookLive版だと思えば大体あってます。

使い方

  1. 上のコードをコピーします。
  2. http://booklive.jp/my/top に移動してBookLiveにログインします。
    • すでにログイン済みならトップページで実行しても問題ないはず。
  3. 開発者コンソール出す。
    • IE/Firefox/Chromeなど主要ブラウザなら「F12」キーで出せます。
  4. 貼り付けてEnter。
  5. コンソールに結果が出力されます。


動作確認はIE11でしかしていません。
たぶん他でも動くと思いますが、Firefoxさん辺りは実行前に警告文出すかも。

IE/Firefox/Chromeのそれぞれ最新版で正常に動作することを確認しました。

ソース

実際に実行されるスクリプトの中身です。
呼び出すソースはDropboxにアップロードしたものですが、コードは同一です。

main();

function main() {
    var now = new Date();
    var startYear = now.getFullYear();
    var startMonth = now.getMonth() + 1;
    
    var paid = 0;
    var least = prompt("何年まで遡りますか?", startYear);
    if (!/^[0-9]{4}$/.test(least)) {
        console.warn("半角4文字の西暦で入力してください。");
        return;
    }
    
    for (var y = startYear + 1; --y >= least; ) {
        for (var m = (y === startYear ? startMonth : 12) + 1; --m > 0; ) {
            paid += monthSum(y, m);
            console.info("ここまでの累計: " + paid + "円");
        }
    }
    console.info("合計: " + paid + "円");
}

function monthSum(year, month) {
    var sum = 0;
    console.info(year + "年" + month + "月のリクエスト中...");
    var doc = request("/my/product?year=" + year + "&month=" + month);
    var nodes = doc.querySelectorAll('[id^="myproduct_display"]');
    
    if (nodes.length !== 0) {
        sum = [].slice.call(nodes)
            .map(function(e){ return e.textContent.trim(); })
            .filter(function(text){ return text.indexOf("円") === text.length - 1; })
            .map(function(price){ return parseInt(price.split(",").join("")); })
            .reduce(function(a, b){ return a + b; });
    }
    
    console.info(sum + "円");
    return sum;
}

function request(url) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, false);
    xhr.send(null);
    
    var range = document.createRange();
    return range.createContextualFragment(xhr.responseText);
}

ちなみに

私の消費金額は2013年1月から7月12日現在で654,126円らしいです。

iPadのリファビッシュ品のリスト取ってくるだけのexe作った

単に自分が欲しかったので。
手元のiPad miniがストレージもメモリもカツカツで、使い物にならなくなりつつあるからです。
TouchIDなんか必要ないので、iPad mini Retinaが安く手に入ればいいかなと。

Dropbox - iPadRefubished.exe
何も特別なことはしていないためソースコードは省略しますが、どうしても見たい物好きな人がいたら、ILSpyとかJustDecompileとかで勝手に覗いてください。

一時的に使うために作ったため、メソッド分割などしていません。すごく適当です。
一応処理の流れを言うと

  1. http://store.apple.com/jp/browse/home/specialdeals/ipadスクレイピング
  2. 各製品の名前とリンクURLと価格と詳細情報を抜き出す
  3. LinkLabelコンポーネントに名前とURLを貼り付ける
  4. Labelコンポーネントに価格貼り付けて右寄せ
  5. ToolTipコンポーネントに詳細貼り付けてLinkLabelに関連付け

以上。

Paizaのオンラインハッカソンやってみた

土日特にすることもなく暇なので遊んでた。paiza.jp

1問目

与えられた文字列の基数番目のみを取り出せという問題。
使用言語: Ruby

input_lines = gets
chars = input_lines.each_char
odds = chars.select.with_index do |e, i| i % 2 == 0 end
puts odds.join

これは非常に簡単。どの言語使おうがすぐ書けます。
もしかしたら、「え?2で割り切れるのは偶数じゃないの?」と思うかもしれません。
ただ、プログラミング言語の世界ではインデックスは0から始まるのが主流であるため、この場合は逆転します。

2問目

曜日ごとの売り上げデータが改行区切りで流れてくるのでそれぞれ集計してね、という問題。
使用言語: C#

using System;
using System.Collections.Generic;
using System.Linq;

public class Hello{
    public static void Main(){
        var line = System.Console.ReadLine();
        int max = int.Parse(line);
        
        var weekDays = (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek));
        var eachWeekDays = new Dictionary<DayOfWeek, List<int>>();
        foreach (DayOfWeek weekDay in weekDays)
        {
            eachWeekDays[weekDay] = new List<int>();
        }
        
        for (int i = 0; i < max; i++)
        {
            int value = int.Parse(Console.ReadLine());
            var week = (DayOfWeek)((i + 1) % 7);
            eachWeekDays[week].Add(value);
        }
        
        new[]{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}
        .Select(s => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), s))
        .Select(dow => eachWeekDays[dow])
        .ToList().ForEach(list => Console.WriteLine(list.Sum()));
    }
}

System.DayOfWeek列挙体にキャストするために無駄に回りくどいコードになってしまいました。
曜日ごとにList<int>を作って、それぞれに加算していって、最後に合計して出力、と。
正直読みにくい。こんなコード書いた奴は死ねばいいと思う。

3問目

RENA または MINAMI のどちらかを出力しろ、という問題。
正直Brainf*ck使っても5分以内に解ける自信があります。
コメントするべきことがないです。
使用言語: Ruby

puts :RENA

Rubyを使った理由は文字数が少なくて済むから。

4問目

RENAを選んだ場合の問題。
表計算ソフトの架空のシートで、選択した範囲の合計値を出力しろという問題。
選択範囲は複数の場合もありますが、セルの内容はすべて自然数です。
使用言語: C#

using System;
using System.Collections.Generic;
using System.Linq;

public class Hello
{
    public static void Main()
    {
        int[] intValues = Console.ReadLine().Trim().Split(' ').Select(int.Parse).ToArray();
        int columns     = intValues[0];
        int rows        = intValues[1];
        int selections  = intValues[2];
        
        Cell[,] cells = new Cell[columns, rows];
        
        for (int y = 0; y < rows; y++)
        {
            int[] values = Console.ReadLine().Trim().Split(' ').Select(int.Parse).ToArray();
            for (int x = 0; x < columns; x++)
            {
                cells[x, y].X = x;
                cells[x, y].Y = y;
                cells[x, y].Value = values[x];
            }
        }
        
        var selected = new HashSet<Cell>();
        for (int i = 0; i < selections; i++)
        {
            int[] positions = Console.ReadLine().Trim().Split(' ').Select(v => int.Parse(v) - 1).ToArray();
            int startX = positions[0];
            int startY = positions[1];
            int endX   = positions[2];
            int endY   = positions[3];
            
            cells.Cast<Cell>().Where(c =>
            {
                return
                    startX <= c.X && c.X <= endX &&
                    startY <= c.Y && c.Y <= endY;
            })
            .ToList().ForEach(c => selected.Add(c));
        }
        
        Console.WriteLine(selected.Sum(c => c.Value));
    }
}

struct Cell
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Value { get; set; }
}

シート上のセルを表現するCell構造体を定義し、Cell型を持つ2次元配列を宣言、すべての要素に代入していきます。
選択範囲に含まれるセルをHashSet<Cell>に追加していきます。
HashSetクラス自体が内包するコレクションの重複を許さないため、追加する際に既に追加したかどうか、などと気にする必要がなくて楽です。
そしてHashSetクラスも当然のようにIEnumerable<T>を実装しているためLINQが使えます。
Cell構造体のValueプロパティを合計して出力、と。

余談ですが、この問題の問題文に書かれたプログラミングコードが「なでしこ」で書かれていて、コメントがいちいちエイプリルフールでやれといわんばかりの内容なので、是非一度見てみるといいと思います。

5問目

MINAMIを選んだ場合の問題。

まだやってません。
寝て起きたらやってみたいです。