2水準系直行表の作成

2水準系直行表を作成するスクリプト

require 'terminal-table'
require 'csv'

def dup_row(rows)
  new_rows = []
  rows.each do |row|
    2.times do
      new_rows << row.dup
    end
  end
  new_rows
end

def add_cols(rows)
  c = [0,1].cycle
  new_rows = rows.dup
  new_rows.each do |row|
    d = c.next
    add_row = []
    row.each do |v|
      add_row << (v ^ d)
    end
    row << d
    row.concat(add_row)
  end
  new_rows
end

count = ARGV[0].to_i

rows = [[0], [1]]

Math.log2(count).to_i.times do
  rows = add_cols(dup_row(rows))
end

format = ARGV[1]

if format == "terminal"
  table = Terminal::Table.new do |t|
    t.headings = ["No."].concat rows.first.size.times.map { |i| i+1 }
    t.rows = rows.map.with_index do |row, i|
      ["##{i+1}"].concat(row)
    end
  end
  puts table
else
  csv_rows = []
  header = ["No."].concat rows.first.size.times.map { |i| i+1 }
  rows.each.with_index do |row, i|
    csv_rows << CSV::Row.new(header, ["##{i+1}"].concat(row))
  end
  table = CSV::Table.new(csv_rows)
  puts table
end

コマンド

第一引数は因子数

第二引数は表示形式。 terminal を指定すると表形式で表示。それ以外は CSVで出力

> ruby main.rb 15 terminal

結果

+-----+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+
| No. | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
+-----+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+
| #1  | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0  | 0  | 0  | 0  | 0  | 0  |
| #2  | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1  | 1  | 1  | 1  | 1  | 1  |
| #3  | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0  | 0  | 1  | 1  | 1  | 1  |
| #4  | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1  | 1  | 0  | 0  | 0  | 0  |
| #5  | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1  | 1  | 0  | 0  | 1  | 1  |
| #6  | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 0  | 0  | 1  | 1  | 0  | 0  |
| #7  | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1  | 1  | 1  | 1  | 0  | 0  |
| #8  | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0  | 0  | 0  | 0  | 1  | 1  |
| #9  | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0  | 1  | 0  | 1  | 0  | 1  |
| #10 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 1  | 0  | 1  | 0  | 1  | 0  |
| #11 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0  | 1  | 1  | 0  | 1  | 0  |
| #12 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1  | 0  | 0  | 1  | 0  | 1  |
| #13 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1  | 0  | 0  | 1  | 1  | 0  |
| #14 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0  | 1  | 1  | 0  | 0  | 1  |
| #15 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1  | 0  | 1  | 0  | 0  | 1  |
| #16 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0  | 1  | 0  | 1  | 1  | 0  |
+-----+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+

子プロセス間でパイプをつなぐ

親プロセスで作成したパイプを子プロセス間でつなぐ方法

以下のスクリプトは、標準入力で 何か文字を入力すると、それが出力されるサンプル。

out_r, out_w = IO.pipe

script1 = <<~SCRIPT1
$stdout.sync = true
loop do
  v = gets
  puts '1:' + v
  sleep 1
end
SCRIPT1
spawn("ruby", "-e", script1, { :out => out_w })

script2 = <<~SCRIPT2
loop do
  puts '2:' + gets
  sleep 1
end
SCRIPT2
spawn("ruby", "-e", script2, { :in => out_r })

loop do
  sleep(1)
end

Windowsファイルパスを標準添付ライブラリで生成/パースする時の言語ごとの違い

RubyC#Go の標準添付ライブラリで file:///c:\temp\doc.txt のようなパスを生成・パースした時の違いを示します。Ruby だけは標準ライブラリの仕様上少し手を加えてあげないと動きませんでした。そのあたり少しフェアじゃないかもしれません。

参考サイト

dobon.net

pkg.go.dev

docs.ruby-lang.org

docs.ruby-lang.org

C#

using System;

namespace ConsoleApp22
{
    class Program
    {
        static void GenUrl(string winPath)
        {
            Uri u = new Uri(winPath);
            Console.WriteLine(u.AbsoluteUri);
        }

        static void GenPath(string urlPath)
        {
            Uri u = new Uri(urlPath);
            string winPath = u.LocalPath + Uri.UnescapeDataString(u.Fragment);
            Console.WriteLine(winPath);
        }

        static void Main(string[] args)
        {
            GenUrl(@"c:\temp\テスト doc#1.txt");
            // file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt

            GenPath(@"file:///c:\temp\テスト doc#1.txt");
            // c:\temp\テスト doc#1.txt
        }
    }
}

Go

package main

import (
    "fmt"
    "net/url"
)

func GenUrl(winPath string) {
    u := &url.URL{}
    u.Scheme = "file"
    u.Path = winPath
    fmt.Println(u.String())
}

func GenPath(urlPath string) {
    u, _ := url.Parse(urlPath)
    fmt.Println(u.Path + "#" + u.Fragment)
}

func main() {
    GenUrl("c:\\temp\\テスト doc#1.txt")
    // file://c:%5Ctemp%5C%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt
    GenPath("file:///c:\\temp\\テスト doc#1.txt")
    // /c:\temp\テスト doc#1.txt
}

Ruby

require 'URI'

def escape_path(path)
  path.gsub(/\\/, "\/").split("/").map { |elem|
    URI.encode_www_form_component(elem)
  }.join("/")
end

def gen_url(win_path)
  uri = URI::Generic.build({
    scheme: "file",
    path: escape_path("/#{win_path}")
  })
  puts uri.to_s
end

def gen_path(url_path)
  uri = URI.parse(escape_path(url_path.gsub(/^file:\/\/\//, "")))
  puts uri.path
end

def decode_gen_path(url_path)
  uri = URI.parse(escape_path(url_path.gsub(/^file:\/\/\//, "")))
  puts URI.decode_www_form_component(uri.path)
end

gen_url(%q|c:\temp\テスト doc#1.txt|)
# file:///c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt

gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|)
# c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt

decode_gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|)
# c:/temp/テスト doc#1.txt

結果

言語 生成されたパス
C# file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt
Go file://c:%5Ctemp%5C%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt
Ruby file:///c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt
言語 パースされたパス
C# c:\temp\テスト doc#1.txt
Go /c:\temp\テスト doc#1.txt
Ruby(デコードなし) c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt
Ruby(デコードあり) c:/temp/テスト doc#1.txt

やはり C# はすごいきれいに作れる印象があります。

Go は C# 同様に平易に生成・パースができました。ただ、パースした際に頭に / がついてくるので実際にファイルパスとして使用するときは一工夫が必要そうです。

RubyWindowsはあまり得意ではないプラットフォームであることから自然には生成・パースはできませんでした。生成・パースには工夫が必要でした。ただ、Rubyaddressable という gem によって簡単に生成・パースできるようになるため、そこまで問題ではないと思いました。

おまけ

addressable を使用した Ruby の例を以下に示します。

require 'addressable/uri'

def gen_url(win_path)
  uri = Addressable::URI.convert_path(win_path)
  puts uri.to_s
end

def gen_path(url_path)
  uri = Addressable::URI.parse(url_path)
  puts uri.path
end

gen_url(%q|c:\temp\テスト doc#1.txt|)
# file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc#1.txt

gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|)
# /c:\temp\テスト doc

プラットフォームがWindowsかそうでないかで処理を変える方法

RUBY_PLATFORMRuby が動作しているプラットフォームがわかります。

Windowsx64-mingw32 とかになります。windows ではありません。

Visual Studio とかでビルドすると mswin が入るようです。(環境がないのでフルネームはわかりません...)

なので、これらの文字列が RUBY_PLATFORM に含まれているかどうかを確認することで Windwos で動作しているかが判定できます。

def bar(msg)
  puts "#{__callee__} #{msg}"
end

3.times do |i|
  if RUBY_PLATFORM =~ /mingw|mswin/
    bar("win: #{i}")
  else
    bar("other: #{i}")
  end
end

何回も呼ぶようなメソッドを包含するような場合だと、定義ごと分岐したほうがいいかもしれません。

if RUBY_PLATFORM =~ /mingw|mswin/
  def foo(i)
    puts "#{__callee__} win: #{i}"
  end
else
  def foo(i)
    puts "#{__callee__} other: #{i}"
  end
end

3.times do |i|
  foo(i)
end

複数のメソッドを定義しないといけない場合は上記の場合だと煩雑になるかもしれません。その場合は require を分岐させる方法がいいかもしれません。

if RUBY_PLATFORM =~ /mingw|mswin/
  require File.join(__dir__, "method_win.rb"
else
  require File.join(__dir__, "method.rb"
end

3.times do |i|
  method(i)
end

Windowsでプロセスグループを指定して spawn する方法

Linux で プロセスグループを指定して spawn を実行する場合は pgroup を指定しますが、Windowsではこのオプションはサポートされていません。Windowsでプロセスグループを作成するには new_pgroup を指定します。ただし、spawnの記述を間違うとshell経由でプロセスが起動してしまうため、このオプションが機能しなくなるため注意が必要です。

Ruby プログラム(間違い)

spawnの第一引数にコマンドライン引数も含めて渡してしまうとshell経由での起動となるため、Ctrl-Cがshellに伝わってしまってshellごと子プロセスが終了する。

a.rb

spawn "ruby b.rb", new_pgroup: true

loop do
  print "test\n"
  sleep(1)
rescue Interrupt
  exit(false)
end

b.rb

loop do
  print "#{Time.now}\n"
  sleep(1)
end

Ruby プログラム(shellを経由しない場合)

spawnにコマンドと引数を分けて渡してやると、shellを経由しなくなる。複数の引数を渡したい場合は、それぞれを分けて渡すこと。

a.rb

spawn "ruby", "b.rb", new_pgroup: true

loop do
  print "test\n"
  sleep(1)
rescue Interrupt
  exit(false)
end

調査用 Go プログラム (参考)

// +build ignore

package main

import (
    "fmt"
    "os/exec"
    "syscall"
    "time"
)

const (
    CREATE_NEW_PROCESS_GROUP = 0x00000200
)

func main() {
    var cmd *exec.Cmd
    cmd = exec.Command("ruby", "-e", "'sleep'")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: CREATE_NEW_PROCESS_GROUP,
    }
    cmd.Start()
    time.Sleep(1 * time.Second)
    fmt.Println(cmd.Process.Pid)

    for {
        time.Sleep(1 * time.Second)
    }
}

調査用 C言語 プログラム (参考)

#include <stdio.h>
#include <Windows.h>
#include <string.h>
#include <wchar.h>

void main()
{
    BOOL fRet;
    STARTUPINFO aStartupInfo;
    PROCESS_INFORMATION aProcessInformation;
    SECURITY_ATTRIBUTES sa;
    DWORD dwCreationFlags;
    const WCHAR* prog = NULL;
    WCHAR cmd[] = L"ruby C:\\dev\\ruby\\test_spawn\\b.rb";

    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    memset(&aStartupInfo, 0, sizeof(aStartupInfo));
    memset(&aProcessInformation, 0, sizeof(aProcessInformation));
    aStartupInfo.cb = sizeof(aStartupInfo);
    aStartupInfo.dwFlags = STARTF_USESTDHANDLES;
    aStartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    aStartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    aStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);

    dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP;

    printf("start\n");

    fRet = CreateProcess(prog, cmd, &sa, &sa, sa.bInheritHandle, dwCreationFlags, NULL, NULL, &aStartupInfo, &aProcessInformation);

    printf("fRet: %d\n", fRet);

    CloseHandle(aProcessInformation.hThread);

    printf("PID: %d\n", aProcessInformation.dwProcessId);

    WaitForSingleObject(aProcessInformation.hProcess, INFINITE);

    printf("end  \n");
}

参考資料

github.com

spawnとCtrl+C

参考サイト

equj65.net

モチベ

プロセスグループとCtrl+Cの関係がよくわからなかったので調べてみた。

結論

pgroup: false だと子プロセスにも SIGINTを送信する pgroup: true だと子プロセスには SIGINTは送信しない

プロセスグループなしで spawn

以下のプログラムを動かすとします。

a.rb

spawn "ruby b.rb"

begin
  sleep
rescue Interrupt
  puts "interrupt a.rb"
  exit(false)
end

b.rb

begin
  sleep
rescue Interrupt
  puts "interrupt b.rb"
  exit(false)
end

ruby a.rb を実行した後、Ctrl+C を押します。出力は以下のような感じなると思います。

^Cinterrupt b.rb
interrupt a.rb

どちらも終了します。

プロセスグループありで spawn

先ほどの a.rbspawnpgroup: true オプションを付けます。これを付けると b.rba.rb とは別のプロセスグループで動作するようになります。

a.rb

spawn "ruby b.rb", pgroup: true # ← pgroup: true オプションを追加

begin
  sleep
rescue Interrupt
  puts "interrupt a.rb"
  exit(false)
end

先ほどと同様に ruby a.rb を実行した後、Ctrl+C を押します。出力は以下のような感じになると思います。

^Cinterrupt a.rb

ps aux | grep ruby をすると ruby b.rb のプロセスが残っているのがわかります。

以上

Fiddle で MessageBox 表示

Ruby3.0から Win32API ライブラリが廃止になってしまったらしく 久々に Win32API使おうと思ったら使えなくて困った。 代替品として fiddle/import を使ってねということだったの使ってみた。

require "fiddle/import"
require 'fiddle/types'

def L(text)
  text.encode(Encoding::UTF_16LE)
end

module WIN32API
  extend Fiddle::Importer
  dlload 'C:\\Windows\\System32\\user32.dll'
  include Fiddle::Win32Types

  extern 'HWND GetForegroundWindow()'
  extern 'int MessageBoxW(HWND, const wchar_t*, const wchar_t*, UINT)'
end

hwnd = WIN32API.GetForegroundWindow
WIN32API.MessageBoxW(hwnd, L("こんにちわ"), L("hello"), 0)

MessageBoxWの第二引数、第三引数は LPCTSTR なんですが、Fiddle::Win32Types には定義されていないので Rubyが認識できる型で指定してあげる必要があります。LPCTSTR は const wchar_t* なのでそれで置き換えています。

また Rubyは文字列のエンコーディングがデフォルト UTF-8なのですが、wchar_t は UTF-16LE なので MessageBoxWに渡す前にエンコーディングの変更をしてあげる必要があります。

参考資料

qiita.com

news.mynavi.jp