Linux Mint に Julia をインストールする

julialang.org
Linux Mint 19.3 に Julia をインストールしてみます。

インストールはここを参考にしました。まず、Snap をインストールします。

$ sudo apt update
$ sudo apt install snapd

Julia をインストールします。

$ sudo snap install julia --classic
2020-03-05T12:14:16+09:00 INFO Waiting for restart...
julia 1.0.4 from The Julia Language (julialang✓) installed
$ /snap/bin/julia -version
julia version 1.0.4

これでインストールされましたが、/snap/bin にPATHが通っていないので、面倒です。PATHを通すか、自分は .bashrc に alias julia='/snap/bin/julia' としておきました。

これで、

$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.0.4 (2019-05-16)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> 2020*3
6060

julia> exit()
$ 

こんな感じで REPL が立ち上がります。
 

ちょっとREPLで遊んでみた

julia> a = 10
10

julia> 2a
20

julia> 4.5a
45.0

julia> typeof(a)
Int32

julia> typeof(4.5a)
Float64

julia> cube(x) = x^3
cube (generic function with 1 method)

julia> cube(a)
1000

julia> f(x) = 3x - a
f (generic function with 1 method)

julia> f(20)
50

julia> sphere(x) = 4/3 * pi * cube(x)
sphere (generic function with 1 method)

julia> sphere(10)
4188.790204786391

julia> b = [x for x = 1:4]
4-element Array{Int32,1}:
 1
 2
 3
 4

julia> c = b * 3
4-element Array{Int32,1}:
  3
  6
  9
 12

julia> b + c
4-element Array{Int32,1}:
  4
  8
 12
 16

julia> d = vcat(b, c)
8-element Array{Int32,1}:
  1
  2
  3
  4
  3
  6
  9
 12

julia> filter(iseven, d)
4-element Array{Int32,1}:
  2
  4
  6
 12

julia> str = map(x -> repeat("*", x), b)
4-element Array{String,1}:
 "*"   
 "**"  
 "***" 
 "****"

julia> join(str, " ")
"* ** *** ****"

julia> a = 45
45

julia> f(20)
15

julia> g(x) = 2x - j
g (generic function with 1 method)

julia> g(3)
ERROR: UndefVarError: j not defined
Stacktrace:
 [1] g(::Int32) at ./REPL[22]:1
 [2] top-level scope at none:0

julia> j = 100
100

julia> g(3)
-94

julia> j = 10
10

julia> g(3)
-4

関数はクロージャなのだな。それから、関数内に未定義変数があっても、実行するまでは怒られないのか。
 

Ruby は「痒いところに手が届く」か

qiita.comいや、Python すばらしいですね。どこまで Ruby でできるか、試してみました。なお、使った Ruby のバージョンは 2.7.0 です。
 

3値以上の比較

1 == 2 == 3  # -> False
1 < 2 < 3 < 4  # -> True

いやー、これは Ruby ではできないですね。なお、Ruby でもこれができるバージョンがあったそうですが、まつもとさんがふつうの(?)比較に戻したそうです。
 

時間(datetime/date)の比較

Python

from datetime import date

feb1 = date(2020, 2, 1)
feb2 = date(2020, 2, 2)
feb3 = date(2020, 2, 3)

feb1 < feb2 <= feb2 < feb3  # -> True

Rubyrequire "date" します。

irb(main):001:0> require "date"
=> true
irb(main):002:0> feb1 = Date.new(2020, 2, 1)
irb(main):003:0> feb2 = Date.new(2020, 2, 2)
irb(main):004:0> feb3 = Date.new(2020, 2, 3)
irb(main):005:0> feb1 < feb2 && feb2 < feb3
=> true

だいたい同じですかね。
 

時間の最大/最小

Pyhton。

from datetime import date

# 意図的に逆順にしてます
dates = [
    date(2020, 2, 3),
    date(2020, 2, 2),
    date(2020, 2, 1),
]

min(dates)  # -> datetime.date(2020, 2, 1)
max(dates)  # -> datetime.date(2020, 2, 3)

Ruby

irb(main):001:0> require "date"
=> true
irb(main):002:0> dates = 3.downto(1).map {|i| Date.new(2020, 2, i)}
irb(main):003:0> dates.min
=> #<Date: 2020-02-01 ((2458881j,0s,0n),+0s,2299161j)>
irb(main):004:0> dates.max
=> #<Date: 2020-02-03 ((2458883j,0s,0n),+0s,2299161j)>

これもだいたい同じですかね。
 

時間の計算

Python

# Input
from datetime import datetime

start = datetime(2020, 2, 1, 10)
goal = datetime(2020, 2, 3, 12)

t = goal - start
print(f'あなたの記録は{t.days}日と{t.seconds}秒です')

# Output
'あなたの記録は2日と7200秒です'

Ruby

irb(main):001:0> require "date"
=> true
irb(main):002:0> start = DateTime.new(2020, 2, 1, 10)
irb(main):003:0> goal = DateTime.new(2020, 2, 3, 12)
irb(main):004:0> t = goal - start
irb(main):005:0> "あなたの記録は#{t.to_i}日と#{(t % 1 * 86400).to_i}秒です"
=> "あなたの記録は2日と7200秒です"

これはちょっと面倒ですねえ。もっとうまいやり方はないかな。
 

時間を連想配列のキーにする

Python

# Input
from datetime import date

counts = {
    date(2020, 2, 1): 0,
    date(2020, 3, 1): 0,
}
counts[date(2020, 2, 1)] += 1
counts[date(2020, 2, 1)] += 1
counts[date(2020, 3, 1)] += 1

print(counts)

# Output
{datetime.date(2020, 2, 1): 2, datetime.date(2020, 3, 1): 1}

Ruby

irb(main):001:0> require "date"
=> true
irb(main):002:1* counts = {
irb(main):003:1*   Date.new(2020, 2, 1) => 0,
irb(main):004:1*   Date.new(2020, 3, 1) => 0,
irb(main):005:0> }
irb(main):006:0> counts[Date.new(2020, 2, 1)] += 1
irb(main):007:0> counts[Date.new(2020, 2, 1)] += 1
irb(main):008:0> counts[Date.new(2020, 3, 1)] += 1
irb(main):009:0> counts
=> {#<Date: 2020-02-01 ((2458881j,0s,0n),+0s,2299161j)>=>2,
    #<Date: 2020-03-01 ((2458910j,0s,0n),+0s,2299161j)>=>1}

ほぼ同じ。
 

キーが連想配列に含まれるか

Python

d = {
    'foo': 1,
    'bar': 2,
    'baz': 3,
}

print('foo' in d)  # -> True

Ruby

irb(main):001:0> h = {foo: 1, bar: 2, baz: 3}
irb(main):002:0> h.has_key?(:foo)
=> true

わかりやすいですね。
 

連想配列からキーを抽出

Python

d = {
    'foo': 1,
    'bar': 2,
    'baz': 3,
}
print(list(d))  # -> ['foo', 'bar', 'baz']

Ruby

irb(main):001:0> h = {foo: 1, bar: 2, baz: 3}
irb(main):002:0> h.keys
=> [:foo, :bar, :baz]

これもわかりやすいですね。
 

連想配列から値を抽出

Python

d = {
    'foo': 1,
    'bar': 2,
    'baz': 3,
}
print(list(d.values()))  # -> [1, 2, 3]

Ruby

irb(main):001:0> h = {foo: 1, bar: 2, baz: 3}
irb(main):002:0> h.values
=> [1, 2, 3]

これもわかりやすいですね。
 

連想配列からキーと値のペアを抽出

Python

d = {
    'foo': 1,
    'bar': 2,
    'baz': 3,
}

for key, value in d.items():
    print(key, value)

# Output
foo 1
bar 2
baz 3

Ruby

irb(main):001:0> h = {foo: 1, bar: 2, baz: 3}
irb(main):002:0> puts h.map {_1.join(" ")}
foo 1
bar 2
baz 3

map を使うのは Ruby らしいのでは? もちろんふつうに each あるいは each_pair で取り出すこともできます。
 

2重配列を連想配列へ変換

Python

# Input
l = [
    ['Yamada', 'baseball'],
    ['Tanaka', 'soccer'],
    ['Sato', 'tennis'],
]
dict(l)

# Output
{'Yamada': 'baseball', 'Tanaka': 'soccer', 'Sato': 'tennis'}

Ruby

irb(main):001:1* ary = [
irb(main):002:1*   [:Yamada, :baseball],
irb(main):003:1*   [:Tanaka, :soccer],
irb(main):004:1*   [:Sato, :tennis],
irb(main):005:0> ]
irb(main):006:0> ary.to_h
=> {:Yamada=>:baseball, :Tanaka=>:soccer, :Sato=>:tennis}

ほぼ同じですね。
 

多重配列のループ

Python

# Input
rows = [
    ['yamada', 20],
    ['tanala', 18],
    ['sato', 18],
]

for name, age in rows:
    print(f'{name}さんは{age}歳です')
else:
    print('紹介終わり')

# Output
'yamadaさんは20歳です'
'tanalaさんは18歳です'
'satoさんは18歳です'
'紹介終わり'

Ruby

irb(main):001:1* rows = [
irb(main):002:1*   [:yamada, 20],
irb(main):003:1*   [:tanala, 18],
irb(main):004:1*   [:sato, 18],
irb(main):005:0> ]
irb(main):006:0* puts rows.map {|name, age| "#{name}さんは#{age}歳です"} + ["紹介終わり"]
yamadaさんは20歳です
tanalaさんは18歳です
satoさんは18歳です
紹介終わり

いいんじゃないでしょうか。
 

多重配列から必要な要素だけ取り出しつつループ

Python

# Input
l = [
    ['Yamada', 'Taro', 20, 'baseball'],
    ['Tanaka', 'Jiro', 18, 'circle'],
]

# 先頭を取り出す
for last, *others in l:
    print(last, others)
print()

# 末尾を取り出す
for *others, circle in l:
    print(circle, others)
print()

# 最初の2要素を取り出す
# (他の要素が要らない場合はダブルアンダースコアを指定するのがPython流)
for last, first, *__ in l:
    print(last, first)

# Output
Yamada ['Taro', 20, 'baseball']
Tanaka ['Jiro', 18, 'circle']

baseball ['Yamada', 'Taro', 20]
circle ['Tanaka', 'Jiro', 18]

Yamada Taro
Tanaka Jiro

Ruby

irb(main):001:1* ary = [
irb(main):002:1*   [:Yamada, :Taro, 20, :baseball],
irb(main):003:1*   [:Tanaka, :Jiro, 18, :circle],
irb(main):004:0> ]
irb(main):005:0> #先頭を取り出す
irb(main):006:0> puts ary.map {|last, *others| "#{last} #{others.inspect}"}
Yamada [:Taro, 20, :baseball]
Tanaka [:Jiro, 18, :circle]
irb(main):007:0> #末尾を取り出す
irb(main):008:0> puts ary.map {|*others, circle| "#{circle} #{others.inspect}"} 
baseball [:Yamada, :Taro, 20]
circle [:Tanaka, :Jiro, 18]
irb(main):009:0> #最初の2要素を取り出す
irb(main):010:0> puts ary.map {|last, first, *_| "#{last} #{first}"}
Yamada Taro
Tanaka Jiro

ほとんど同じだなあ。
では Python

# Input
l = [
    ['a', 'b', 'c', ['d', 'e', 'f']],
]

for one, *__, (*__, two) in l:
    print(one, two)

# Output
a f

Ruby では? これはせっかくなので、Ruby 2.7.0 のパターンマッチを使ってみましょう。

irb(main):001:0> ary = [:a, :b, :c, [:d, :e, :f]]
irb(main):002:1* case ary
irb(main):003:1*   in [one, _, _, [_, _, two]]
irb(main):004:0> end
irb(main):005:0> "#{one} #{two}"
=> "a f"

おお、いいですね。
 

カウンター付きループ

Python

# Input
rows = [
    ['Yamada', 20],
    ['Tanaka', 18],
    ['Sato', 16],
]

for i, (name, age) in enumerate(rows, start=1):
    print(f'{i}行目 : 氏名={name}, 年齢={age}')

# Output
'1行目 : 氏名=Yamada, 年齢=20'
'2行目 : 氏名=Tanaka, 年齢=18'
'3行目 : 氏名=Sato, 年齢=16'

Ruby

irb(main):001:1* rows = [
irb(main):002:1*   [:Yamada, 20],
irb(main):003:1*   [:Tanaka, 18],
irb(main):004:1*   [:Sato, 16],
irb(main):005:0> ]
irb(main):006:1* puts rows.map.with_index(1) {|(name, age), i|
irb(main):007:1*   "#{i}行目:氏名=#{name}, 年齢=#{age}"
irb(main):008:0> }
1行目:氏名=Yamada, 年齢=20
2行目:氏名=Tanaka, 年齢=18
3行目:氏名=Sato, 年齢=16

Ruby らしいですね。
 

連想配列のキー無しエラーを防ぐ

Python

d = {
    'Yamada': 20,
    'Tanaka': 18
}
d.get('Yamada')  # -> 20
d.get('Sato')  # -> None
d.get('Sato', '年齢なし')  # -> 年齢なし

Ruby。これは Python とは挙動がちがいますね。どちらがよいでしょうか。

irb(main):001:0> h = Hash.new("年齢なし")
irb(main):002:0> h.merge!({Yamada: 20, Tanaka: 18})
=> {:Yamada=>20, :Tanaka=>18}
irb(main):003:0> h[:Yamada]
=> 20
irb(main):004:0> h[:Sato]
=> "年齢なし"

 

配列/連想配列を展開して関数へ渡す

Python

# Input
def func(a, b, c=None, d=None):
    print(a)
    print(b)
    print(c)
    print(d)


l = ['aaa', 'bbb']
d = {'c': 'ccc', 'd': 'ddd'}

func(*l, **d)

# Output
aaa
bbb
ccc
ddd

Ruby。キーワード引数は Ruby 2.7 で整理されました。

irb(main):001:1* def func(a, b, c: nil, d: nil)
irb(main):002:1*   puts a
irb(main):003:1*   puts b
irb(main):004:1*   puts c
irb(main):005:1*   puts d
irb(main):006:0> end
=> :func
irb(main):007:0> ary = [:aaa, :bbb]
irb(main):008:0> h = {c: :ccc, d: :ddd}
irb(main):009:0> func(*ary, **h)
aaa
bbb
ccc
ddd

ほぼ一緒ですね。
 

all関数

Python

l = [
    True,
    1,
    "foo",
]
all(l)  # -> True

l = [
    True,
    1,
    "",
]
all(l)  # -> False

Ruby。Enumerable#all? メソッドがあります。

irb(main):001:0> ary = [true, 1, "foo"]
irb(main):002:0> ary.all?
=> true
irb(main):003:0> ary = [true, 1, nil]
irb(main):004:0> ary.all?
=> false

ほぼ同じなのだけれど、Ruby では空文字列は true なので、少し変えました。
 

any関数

all関数の場合とほぼ同じなので、省略します。Ruby では Enumerable#any? を使います。one? や none? メソッドもあります。
 

集合演算(Set型)

Ruby では require "set" をすれば Python とほぼ同じなので省略。
 

collections / itertools パッケージ

collections.Counter

Python。同一要素の数を数えます。

# Input
import collections

l = ['a', 'b', 'c', 'a', 'a', 'c']
c = collections.Counter(l)
print(c.most_common())

# Output
[('a', 3), ('c', 2), ('b', 1)]

RubyRuby 2.7 から Enumerable#tally が入りました。

irb(main):001:0> ary = [:a, :b, :c, :a, :a, :c]
irb(main):002:0> ary.tally
=> {:a=>3, :b=>1, :c=>2}

 

collections.defaultdict

Python

# Input
import json
import collections

# defaultdict()の引数には関数(callable)ならなんでも渡せる
groups = collections.defaultdict(list)

# 普通の連想配列だと "baseballというキーは存在しない" といったエラーが発生する
groups['baseball'].append('yamada')
groups['tennis'].append('tanaka')
groups['baseball'].append('sato')

print(json.dumps(groups))

# Output
{"baseball": ["yamada", "sato"], "tennis": ["tanaka"]}

Ruby。Hash の default_proc を使います。

irb(main):001:0> groups = Hash.new {|h, k| h[k] = []}
irb(main):002:0> groups[:baseball] << :yamada
irb(main):003:0> groups[:tennis] << :tanaka
irb(main):004:0> groups[:baseball] << :sato
irb(main):005:0> groups
=> {:baseball=>[:yamada, :sato], :tennis=>[:tanaka]}

また、Python

# Input
import json
from collections import defaultdict

nested = defaultdict(lambda: defaultdict(int))

nested['a']['a'] += 1
nested['a']['a'] += 1
nested['a']['b'] += 1
nested['b']['c'] += 1

print(json.dumps(nested))

# Output
{"a": {"a": 2, "b": 1}, "b": {"c": 1}}

に対しては、Ruby なら

irb(main):001:0> nested = Hash.new {|h, k| h[k] = Hash.new(0)}
irb(main):002:0> nested[:a][:a] += 1
irb(main):003:0> nested[:a][:a] += 1
irb(main):004:0> nested[:a][:b] += 1
irb(main):005:0> nested[:b][:c] += 1
irb(main):006:0> nested
=> {:a=>{:a=>2, :b=>1}, :b=>{:c=>1}}

とすればよいです。
 

itertools.product

Python

# Input
import itertools

a = ['a1', 'a2']
b = ['b1', 'b2', 'b3']
c = ['c1']

list(itertools.product(a, b, c))

# Output
[('a1', 'b1', 'c1'),
 ('a1', 'b2', 'c1'),
 ('a1', 'b3', 'c1'),
 ('a2', 'b1', 'c1'),
 ('a2', 'b2', 'c1'),
 ('a2', 'b3', 'c1')]

Ruby には Array#product があります。

irb(main):001:0> a = [:a1, :a2]
irb(main):002:0> b = [:b1, :b2, :b3]
irb(main):003:0> c = [:c1]
irb(main):004:0> a.product(b, c)
=> [[:a1, :b1, :c1], [:a1, :b2, :c1], [:a1, :b3, :c1],
    [:a2, :b1, :c1], [:a2, :b2, :c1], [:a2, :b3, :c1]]

 

itertools.chain.from_iterable

Python

# Input
import itertools

l = [
    ['a1', 'a2'],
    ['b1', 'b2', 'b3'],
]

list(itertools.chain.from_iterable(l))

# Output
['a1', 'a2', 'b1', 'b2', 'b3']

これは Ruby では Array#flatten でおしまいです。

irb(main):001:1* ary = [
irb(main):002:1*   [:a1, :a2],
irb(main):003:1*   [:b1, :b2, :b3],
irb(main):004:0> ]
irb(main):005:0> ary.flatten
=> [:a1, :a2, :b1, :b2, :b3]

 

ioパッケージ

Python

# Input
import io


def writer(f, text):
    f.write(text)


def printer(f):
    print(f.read())


sio = io.StringIO()

writer(sio, 'foo\n')
writer(sio, 'bar\n')
writer(sio, 'baz\n')

sio.seek(0)
printer(sio)

# Output
foo
bar
baz

Ruby では StringIO を使います。

irb(main):001:0> require "stringio"
=> true
irb(main):002:1* def writer(f, text)
irb(main):003:1*   f.write(text)
irb(main):004:0> end
=> :writer
irb(main):005:1* def printer(f)
irb(main):006:1*   print f.read
irb(main):007:0> end
=> :printer
irb(main):008:0> sio = StringIO.new("tmp", "w+")
irb(main):009:0> writer(sio, "foo\n")
irb(main):010:0> writer(sio, "bar\n")
irb(main):011:0> writer(sio, "baz\n")
irb(main):012:0> sio.rewind
irb(main):013:0> printer(sio)
foo
bar
baz

 

タプル(tuple)

Ruby には immutable なタプルはありません。では、Python

# Input
from collections import defaultdict

data = [
    {'circle': 'baseball', 'name': 'yamada', 'age': 10},
    {'circle': 'baseball', 'name': 'sato', 'age': 10},
    {'circle': 'baseball', 'name': 'suzuki', 'age': 11},
    {'circle': 'tennis', 'name': 'tanaka', 'age': 10},
]

per_circle_age = defaultdict(list)

for v in data:
    k = (v['circle'], v['age'])  # (サークル名, 年齢) というタプルを生成
    per_circle_age[k].append(v['name'])  # タプルをキーにして集計

for (circle, age), members in per_circle_age.items():
    print(f'{circle}に所属している{age}歳のメンバー:{members}')

# Output
"baseballに所属している10歳のメンバー:['yamada', 'sato']"
"baseballに所属している11歳のメンバー:['suzuki']"
"tennisに所属している10歳のメンバー:['tanaka']"

これを Ruby でどうするかというと、ふつうに Array と Hash でいけます。

irb(main):001:1* data = [
irb(main):002:1*   {circle: :baseball, name: :yamada, age: 10},
irb(main):003:1*   {circle: :baseball, name: :sato, age: 10},
irb(main):004:1*   {circle: :baseball, name: :suzuki, age: 11},
irb(main):005:1*   {circle: :teniss, name: :tanaka, age: 10},
irb(main):006:0> ]
irb(main):007:0> per_circle_age = Hash.new {|h, k| h[k] = []}
irb(main):008:1* data.each do |h|
irb(main):009:1*   k = [h[:circle], h[:age]]
irb(main):010:1*   per_circle_age[k] << h[:name]
irb(main):011:0> end
irb(main):012:1* puts per_circle_age.map {|(circle, age), members|
irb(main):013:1*   "#{circle}に所属している#{age}歳のメンバー:#{members.inspect}"
irb(main):014:0> }
baseballに所属している10歳のメンバー:[:yamada, :sato]
baseballに所属している11歳のメンバー:[:suzuki]
tenissに所属している10歳のメンバー:[:tanaka]

 

dataclassesパッケージ

Python

import dataclasses


# frozen=True とすると不変(immutable)なオブジェクトとして扱える
@dataclasses.dataclass(frozen=True)
class User:
    last_name: str
    first_name: str

    def full_name(self):
        return f'{self.last_name} {self.first_name}'


yamada = User(last_name='Yamada', first_name='Taro')
tanaka = User(last_name='Tanaka', first_name='Jiro')

yamada.full_name()  # -> Yamada Taro

# 簡単に連想配列へ変換出来ます
dataclasses.asdict(yamada)  # -> {'last_name': 'Yamada', 'first_name': 'Taro'}

# 比較が可能です
yamada2 = User(last_name='Yamada', first_name='Taro')
yamada == yamada2  # -> True
yamada == tanaka  # -> False
yamada in [yamada2]  # -> True

# "frozen=True" とした場合は値の再代入は出来ません
yamada.last_name = 'Sato'  # -> FrozenInstanceError: cannot assign to field 'last_name'

# immutableなので連想配列のキーとして使えます
d = {yamada: 'foo', tanaka: 'bar'}

# 集合演算(Set型)も可能です
{yamada, tanaka} & {yamada2}  # -> {User(last_name='Yamada', first_name='Taro')}

Ruby なら、Struct クラスを使うとよいのかな。

irb(main):001:1* User = Struct.new(:last_name, :first_name) do
irb(main):002:2*   def full_name
irb(main):003:2*     "#{last_name} #{first_name}"
irb(main):004:1*   end
irb(main):005:0> end
irb(main):006:0> yamada = User.new(:Yamada, :Taro)
irb(main):007:0> tanaka = User.new(:Tanaka, :Jiro)
irb(main):008:0> yamada.full_name
=> "Yamada Taro"
irb(main):009:0> yamada.to_h
=> {:last_name=>:Yamada, :first_name=>:Taro}
irb(main):010:0> yamada2 = User.new(:Yamada, :Taro)
irb(main):011:0> yamada == yamada2
=> true
irb(main):012:0> yamada == tanaka
=> false
irb(main):013:0> h = {yamada => :foo, tanaka => :bar}
irb(main):014:0> [yamada, tanaka] & [yamada2]
=> [#<struct User last_name=:Yamada, first_name=:Taro>]

だいたいいけますね。


どうでしょうか。Ruby もなかなかいけるでしょう?

古い Ruby の define_method

AtCoder の過去問をやっていて、手元では通るコードがことごとく RE になる理由が全然わからなかった。いろいろ考えてみたが、コードはどう考えても正しい気がする。


ふと、自分は横着して何も考えずに Ruby 2.7.0 を使っていたが、AtCoderRuby は 2.3.3 なことにハッと気づいた。たまたま rbenv で Ruby 2.3.3 を既にインストールしてあったので、$ rbenv local で切り替えて Ruby 2.3.3 で実行してみたところ、何とエラーが出る。これだったのか。

詳しくは書かないけれど、2.7.0 のコードは Module#define_method をこんな風に使っていた。

class Hoge
end

a = 1

Hoge.define_method(:output) do
  puts a
end

Hoge.new.output    #=>1

ローカル変数 a をふつうはスコープの外であるメソッド内と共有しようという意図である。まあ、この手のコード自体がよいものかは別だが、とにかくこれは 2.7.0 で動く。


しかしこれ、2.3.3 ではエラーが出る。何故かというと、2.3.3 では Module#define_method がプライベート・メソッドだからである。つまり、レシーバーを付けて呼び出せないようになっている。
解決方法はある。Object#send を使えばよい。これを使えば、御存知のとおりプライベート・メソッドまで呼び出せてしまう。

Hoge.send(:define_method, :output) do
  puts a
end

これで AtCoder でも動いた。

なお、Module#defile_method がパブリック・メソッドになったのは、たぶん 2.5.0 からである。るりまで調べてみた。

ブレゼンハムのアルゴリズム(Ruby)

ja.wikipedia.org「ブレゼンハムのアルゴリズム」とは画面に線分を描くアルゴリズムです。コードはここJava 版を移植させていただきました。

Ruby コードです。描画に Cairo を使っています。

require "cairo"

class Draw
  W = 400
  Side = 10
  
  def initialize  
    @surface = Cairo::ImageSurface.new(W, W)
    @context = Cairo::Context.new(@surface)
    
    #背景色
    @context.set_source_color(Cairo::Color::BLACK)
    @context.rectangle(0, 0, W, W)
    @context.fill
  end
  
  def point(x, y)
    @context.set_source_color(Cairo::Color.parse("#73F829"))
    x1 = x * Side
    y1 = y * Side
    @context.rectangle(x1, y1, Side, Side)
    @context.fill
  end
  
  def finish
    @surface.write_to_png("pic.png")
  end
  
  def self.exe(&bk)
    d = Draw.new
    d.instance_exec(&bk)
    d.finish
  end
end


def bresenham(x0, y0, x1, y1)
  dx = x1 - x0
  step_x = (dx >= 0) ? 1 : -1
  dy = y1 - y0
  step_y = (dy >= 0) ? 1 : -1
  
  dx = dx.abs * 2
  dy = dy.abs * 2
  
  @f.point(x0, y0)
  x = x0
  y = y0
  
  if dx > dy
    fraction = dy - dx / 2
    until x == x1
      if fraction >= 0
        y += step_y
        fraction -= dx
      end
      x += step_x
      fraction += dy
      @f.point(x, y)
    end
  else
    fraction = dx - dy / 2
    until y == y1
      if fraction >= 0
        x += step_x
        fraction -= dy
      end
      y += step_y
      fraction += dx
      @f.point(x, y)
    end
  end
end


if $0 == __FILE__
  @f = Draw.new
  bresenham( 3,  3, 35, 15)
  bresenham(35, 15, 18, 37)
  bresenham(18, 37,  3,  3)
  @f.finish
end

メソッドbresenham が求めるものです。Drawクラスは Cairo での描画をしています。

結果はこんな感じです。
20200123012619

Ruby で簡単な Gray-Scott

qiita.comここの Python 版を Ruby に移植しただけです。計算に Gem 'numo-narray' を使っています。画像化には Gem 'gdk_pixbuf2', 'rmagick' を使いました。できたものはこんな感じ。
20191115013736
 
Ruby コード。Ruby 2.6.0, Linux Mint 19.2 で動作確認。
gray_scott.rb

require 'numo/narray'
require 'gdk_pixbuf2'
require 'rmagick'

include Numo

SPACE_GRID_SIZE = 256
VISUALIZATION_STEP = 8
Dx = 0.01
Du = 2e-5
Dv = 1e-5

def laplacian(ary, base)
  #NArray オブジェクトのコピー
  inflow_from_top    = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  inflow_from_bottom = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  inflow_from_left   = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  inflow_from_right  = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  outflow            = base.reshape(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  
  #ラプラシアンの計算
  (SPACE_GRID_SIZE - 1).times do |i|
    inflow_from_top[i + 1, true]  = ary[i, true]
    inflow_from_bottom[i, true]   = ary[i + 1, true]
    inflow_from_left[true, i + 1] = ary[true, i]
    inflow_from_right[true, i]    = ary[true, i + 1]
  end
  outflow = ary * 4
  
  (inflow_from_top + inflow_from_bottom + inflow_from_left +
    inflow_from_right - outflow) / (Dx * Dx)
end

#計算(f, k の値によって反応が変わります)
def calc(u, v, f, k)
  u_base = SFloat.ones(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  v_base = SFloat.zeros(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
  VISUALIZATION_STEP.times do
    partial_u = laplacian(u, u_base) * Du - u * v * v + (1.0 - u) * f
    partial_v = laplacian(v, v_base) * Dv + u * v * v - (f + k) * v
    u += partial_u
    v += partial_v
  end
  [u, v]
end


#初期設定
Dir.chdir("picture")

SQUARE_SIZE = 20

u = SFloat.ones(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
v = SFloat.zeros(SPACE_GRID_SIZE, SPACE_GRID_SIZE)
square_start = SPACE_GRID_SIZE / 2 - SQUARE_SIZE / 2
square_end   = SPACE_GRID_SIZE / 2 + SQUARE_SIZE / 2
u[square_start..square_end, square_start..square_end] = 0.5
v[square_start..square_end, square_start..square_end] = 0.25

#画像の生成
400.times do |i|
  u, v = calc(u, v, 0.022, 0.051)
  
  for_visualize = UInt8.cast(u * 255)
  data = UInt8.zeros(SPACE_GRID_SIZE, SPACE_GRID_SIZE, 3)
  data[true, true, 0] = for_visualize
  data[true, true, 1] = for_visualize
  data[true, true, 2] = for_visualize
  
  pixbuf = GdkPixbuf::Pixbuf.new(data: data.to_string,
              width: SPACE_GRID_SIZE, height: SPACE_GRID_SIZE)
  pixbuf.save("%04d.png" % i)
end

#gif化
list = Magick::ImageList.new
Dir.glob("*").sort.each do |fn|
  list.concat(Magick::ImageList.new(fn))
end
list.delay = 3
list.write("gifanime.gif")

作業用にカレントディレクトリに 'picture' ディレクトリを作って下さい。このコードの場合だと、400ステップ分の画像を生成します。

なお、ラプラシアンの差分方程式化はここなどが参考になります。

※参考
Gray-Scott というのを作ってみた記録 – ✈
Numo::NArray概要 · ruby-numo/numo-narray Wiki · GitHub
 

f = 0.012, k = 0.05 の場合。
20191115014553
 
Gtk によるリアルタイム・アニメーション版はこちら。
https://gist.github.com/obelisk68/ecd97e79b87f69365a0fb0747ffcedca

文字列を一文字づつ飛ばし文字にするのこと(Ruby)

まず、「esehara」という文字列がある。この文字列を「eeaa」といったように、一文字ずつ飛ばし文字にするにはどうしたらいいか

http://bugrammer.hateblo.jp/entry/2016/10/24/175704

あるブログでこんなのを見て考えてみた。

コード例。

def one_jump(str)
  str.chars.each_slice(2).map {|a, *b| a}.join
end

puts one_jump("esehara")    #=>eeaa

こんなのでどうですかね。chars してから each_slice しているので、効率はあまりよくないかも知れない。一応ワンライナーではある。


文字列を一回しか走査しないバージョン(メソッド one_jump1)を書いてベンチマークを取ってみる。

require "benchmark"

def one_jump(str)
  str.chars.each_slice(2).map {|a, *b| a}.join
end

def one_jump1(str)
  flag = true
  result = ""
  str.each_char do |c|
    result << c if flag
    flag = !flag
  end
  result
end


str = [*""..""].join

Benchmark.bm do |x|
 x.report {one_jump(str) }
 x.report {one_jump1(str)}
end

結果。

       user     system      total        real
   0.008000   0.004000   0.012000 (  0.008788)
   0.004000   0.000000   0.004000 (  0.005388)

正確に半分の時間になっているな。しかしどちらも計算量は O(n) だろうから、大してちがわない。one_jump の方が短い Ruby コードのせいか、文字数を大きくすると差が縮まってくることが観測された。