シーケンス処理API比較表(C#→Ruby編)


2014年 06月 26日

問題

昼のお仕事はC#で、
夜のお仕事はRubyで書いていると、
「あっちでできることはこっちでどう書くんだっけ」
と立ち止まることが度々あります。

その都度リファレンスを引けば分かることではあるのですが、
何度も引き直しているとさすがに面倒臭くなってきます。
特にシーケンス処理は言語毎に微妙に差異があって、
注意してないと妙なバグを埋め込むことになりかねません。
どこかに良い感じのシーケンス処理API比較表が転がっていないものでしょうか。

回答

残念ながら軽く検索してみても便利な比較表が見当たらなかったので作りました。

  • 「C#でのアレはRubyだと大体こんなの」という観点でまとめてます。「RubyのアレはC#だと大体こう」というのまで書き始めるとキリがないので省略してます。
  • C#側の戻り値は正直に書くと大変なことになるのでJSON的な何かで書いてます。適宜で読み替えてください。
  • Ruby語への翻訳結果は改善の余地が多々あります。そのうち直します。
API C# Ruby
Aggregate
var ss = new [] {"cocoa", "chino", "rize"};
ss.Aggregate((acc, s) => s + " " + acc)
//==> "rize chino cocoa"
ss = %w(cocoa chino rize)
ss.inject {|acc, s| s + " " + acc}
#==> "rize chino cocoa"
All
new int[] {1, 2, 3}.All(n => n % 2 == 0)
//==> false
new int[] {1, 2, 3}.All(n => n < 10    )
//==> true
new int[] {       }.All(n => false     )
//==> true
[1, 2, 3].all? {|n| n % 2 == 0}
#==> false
[1, 2, 3].all? {|n| n < 10    }
#==> true
[       ].all? {|n| false     }
#==> true
Any
Enumerable.Range(1, 10).Any()
//==> true



Enumerable.Range(1, 0).Any()
//==> false

Enumerable.Range(1, 10).Any(n => n <= 5)
//==> true
Enumerable.Range(1, 10).Any(n => n > 10)
//==> false
(1..10).any? {true}
#==> true
not (1..10).to_a.empty?
#==> true

(1..0).any? {true}
#==> false

(1..10).any? {|n| n <= 5}
#==> true
(1..10).any? {|n| n > 10}
#==> false
AsEnumerable
db.Foos.Where(...).AsEnumerable().Where(...)
# N/A
Average

new [] {1, 2, 3, 4, 5, 6}.Average()
//==> 3.5
xs = [1, 2, 3, 4, 5, 6]
xs.inject(:+) / xs.count.to_f
#==> 3.5
Cast
xs.Cast<Klass>()
//==> IEnumerable<Klass> or InvalidCastException
xs.all? {|x| Klass === x} and
  xs or raise TypeError
Concat
xs.Concat(ys)
// ?
[lazy_xs, lazy_ys].flat_map {|zs| zs}  # lazy
xs + ys  # non-lazy
Contains
xs.Contains(123)
 
xs.include?(123)
xs.member?(123)
Count
var ns = new [] {1, 2, 3, 4, 5};
ns.Count()                 //==> 5
ns.Count(n => n % 2 == 0)  //==> 2
ns = [1, 2, 3, 4, 5]
ns.count                   #==> 5
ns.count {|n| n % 2 == 0}  #==> 2
DefaultIfEmpty
new int[] {1, 2, 3}.DefaultIfEmpty(999)
//==> [1, 2, 3]


new int[] {}.DefaultIfEmpty(999)
//==> [999]
 
ns1 = [1, 2, 3]
ns1.empty? ? [999] : ns1
#==> [1, 2, 3]

ns2 = []
ns2.empty? ? [999] : ns2
#==> [999]
Distinct
new [] {1, 4, 1, 4, 2, 1, 3, 5, 6}.Distinct()
//==> [1, 4, 2, 3, 5, 6]
[1, 4, 1, 4, 2, 1, 3, 5, 6].uniq
#==> [1, 4, 2, 3, 5, 6]
ElementAt
// Works also for lazy sequences
new [] {"a", "b", "c"}.ElementAt(1)
//==> "b"
new [] {"a", "b", "c"}.ElementAt(99)
//==> ArgumentOutOfRangeException
# No direct equivalent for lazy sequences
['a', 'b', 'c'].at(1)
#==> 'b'
['a', 'b', 'c'].at(99)
#==> nil
ElementAtOrDefault
// Works also for lazy sequences
new [] {"a", "b", "c"}.ElementAtOrDefault(1)
//==> "b"
new [] {"a", "b", "c"}.ElementAtOrDefault(99)
//==> null
# No direct equivalent for lazy sequences.
['a', 'b', 'c'].at(1)
#==> 'b'
['a', 'b', 'c'].at(99)
#==> nil
Empty
Enumerable.Empty<T>()
//==> An empty sequence of Ts
# N/A
#
Except
new [] {1, 2, 3}.Except(new [] {2, 4, 6})
//==> [1, 3]
[1, 2, 3] - [2, 4, 6]
#==> [1, 3]
First
new int[] {1, 2, 3}.First()
//==> 1
new int[] {}.First()
//==> InvalidOperationException

new [] {1, 2, 3}.First(n => n % 2 == 0)
//==> 2
new [] {1, 3, 5}.First(n => n % 2 == 0)
//==> InvalidOperationException
[1, 2, 3].first
#==> 1
[].first
#==> nil

[1, 2, 3].find {|n| n % 2 == 0}
#==> 2
[1, 3, 5].find {|n| n % 2 == 0}
#==> nil
FirstOrDefault
new int[] {1, 2, 3}
.FirstOrDefault()
//==> 1
new int[] {}
.FirstOrDefault()
//==> 0

new [] {1, 2, 3}
.FirstOrDefault(n => n % 2 == 0)
//==> 2
new [] {1, 3, 5}
.FirstOrDefault(n => n % 2 == 0)
//==> 0
[1, 2, 3].
first
#==> 1
[].
first
#==> nil

[1, 2, 3].
find {|n| n % 2 == 0}
#==> 2
[1, 3, 5].
find {|n| n % 2 == 0}
#==> nil
GroupBy
Enumerable.Range(1, 10).GroupBy(n => n % 3)
//==> [[1, 4, 7, 10],
//     [2, 5, 8],
//     [3, 6, 9]]
(1..10).group_by {|n| n % 3}
#==> {1=>[1, 4, 7, 10],
#     2=>[2, 5, 8],
#     0=>[3, 6, 9]}
GroupJoin
var a = new {Name = "Alice"};
var b = new {Name = "Bob"};
var c = new {Name = "Ciel"};

var t = new {Name = "Taro", Owner = a};
var j = new {Name = "Jiro", Owner = a};
var s = new {Name = "Shiro", Owner = b};
var k = new {Name = "Kuro", Owner = c};

var owners = new [] {a, b, c};
var pets = new [] {t, j, s, k};

owners.GroupJoin(
  pets,
  o => o,
  p => p.Owner,
  (o, ps) => new {
    Owner = o.Name,
    Pets = ps.Select(p => p.Name)
  }
);
//==> [
//  {"Owner":"Alice","Pets":["Taro","Jiro"]},
//  {"Owner":"Bob","Pets":["Shiro"]},
//  {"Owner":"Ciel","Pets":["Kuro"]}
//]
# TODO
Intersect
var xs = new [] {1, 2, 3, 4, 5};
var ys = new [] {2, 4, 6, 8, 0};
xs.Intersect(ys);  //==> [2, 4]
xs = [1, 2, 3, 4, 5]
ys = [2, 4, 6, 8, 0]
xs & ys  #==> [2, 4]
Join
var a = new {Name = "Alice"};
var b = new {Name = "Bob"};
var c = new {Name = "Ciel"};

var t = new {Name = "Taro", Owner = a};
var j = new {Name = "Jiro", Owner = a};
var s = new {Name = "Shiro", Owner = b};
var k = new {Name = "Kuro", Owner = c};

var owners = new [] {a, b, c};
var pets = new [] {t, j, s, k};

owners.Join(
  pets,
  o => o,
  p => p.Owner,
  (o, p) => new {
    Owner = o.Name,
    Pet = p.Name
  }
);
//==> [{"Owner":"Alice","Pet":"Taro"},
//     {"Owner":"Alice","Pet":"Jiro"},
//     {"Owner":"Bob","Pet":"Shiro"},
//     {"Owner":"Ciel","Pet":"Kuro"}]
# TODO
Last
Enumerable.Range(1, 100).Last()
//==> 100
Enumerable.Empty<int>().Last()
//==> InvalidOperationException

Enumerable.Range(1, 100).Last(n => n % 3 == 0)
//==> 99
Enumerable.Range(1, 100).Last(n => n < 0)
//==> InvalidOperationException
Enumerable.Empty<int>().Last(n => n % 3 == 0)
//==> InvalidOperationException
(1..100).last
#==> 100
[].last
#==> nil

(1..100).find_all {|n| n % 3 == 0}.last
#==> 99
(1..100).find_all {|n| n < 0}.last
#==> nil
[].find_all {|n| n % 3 == 0}.last
#==> nil
LastOrDefault
new int[] {1, 2, 3}.LastOrDefault()
//==> 3
new int[] {}.LastOrDefault()
//==> 0

Enumerable.Range(1, 100)
.LastOrDefault(n => n % 3 == 0)
//==> 99
Enumerable.Range(1, 100)
.LastOrDefault(n => n < 0)
//==> 0
Enumerable.Empty<int>()
.LastOrDefault(n => n % 3 == 0)
//==> 0
(1..100).last
#==> 100
[].last
#==> nil

(1..100).
find_all {|n| n % 3 == 0}.last
#==> 99
(1..100).
find_all {|n| n < 0}.last
#==> nil
[].
find_all {|n| n % 3 == 0}.last
#==> nil
LongCount
var ns = new [] {1, 2, 3, 4, 5};
ns.LongCount()                 //==> 5
ns.LongCount(n => n % 2 == 0)  //==> 2
ns = [1, 2, 3, 4, 5]
ns.count                   #==> 5
ns.count {|n| n % 2 == 0}  #==> 2
Max
new int[] {2, 7, 1, 8, 2}.Max()
//==> 8
new int[] {}.Max()
//==> InvalidOperationException
new int?[] {2, 7, 1, null, 2}.Max()
//==> 7
new int?[] {}.Max()
//==> null

new string[] {"apple", "banana", "cake"}
.Max(s => s.Length)
//==> 6
new string[] {}
.Max(s => s.Length)
//==> InvalidOperationException
new string[] {"apple", null, "cake"}
.Max(s => s == null ? null : (int?)s.Length)
//==> 5
new string[] {}
.Max(s => s == null ? null : (int?)s.Length)
//==> null
[2, 7, 1, 8, 2].max()
#==> 8
[].max()
#==> nil

# N/A

# N/A


['apple', 'banana', 'cake'].
max_by {|s| s.length}
#==> 6
[].
max_by {|s| s.length}
#==> nil
# N/A?


# N/A?

 
Min
new int[] {2, 7, 1, 8, 2}.Min()
//==> 1
new int[] {}.Min()
//==> InvalidOperationException
new int?[] {2, 7, 1, null, 2}.Min()
//==> 1
new int?[] {}.Min()
//==> null

new string[] {"apple", "banana", "cake"}
.Min(s => s.Length)
//==> 4
new string[] {}
.Min(s => s.Length)
//==> InvalidOperationException
new string[] {"apple", null, "cake"}
.Min(s => s == null ? null : (int?)s.Length)
//==> 4
new string[] {}
.Min(s => s == null ? null : (int?)s.Length)
//==> null
[2, 7, 1, 8, 2].min()
#==> 1
[].min()
#==> nil
# N/A

# N/A


['apple', 'banana', 'cake'].
min_by {|s| s.length}
#==> 4
[].
min_by {|s| s.length}
#==> nil
# N/A


# N/A

 
OfType
new object[] {"foo", 123, "bar", 456, "baz"}
.OfType<string>()
//==> ["foo","bar","baz"]
['foo', 123, 'bar', 456, 'baz'].
grep(String)
#==> ["foo", "bar", "baz]
OrderBy
new [] {"apple", "banana", "cake"}
.OrderBy(s => s.Length)
//==> ["cake", "apple", "banana"]
['apple', 'banana', 'cake'].
sort_by {|s| s.length}
#==> ["cake", "apple", "banana"]
OrderByDescending
new [] {"apple", "banana", "cake"}
.OrderByDescending(s => s.Length)
//==> ["banana", "apple", "cake"]
['apple', 'banana', 'cake'].
sort_by {|s| s.length}.reverse()
#==> ["banana", "apple", "cake"]
Range
Enumerable.Range(1, 5)
//==> [1, 2, 3, 4, 5]
Enumerable.Range(5, 5)
//==> [5, 6, 7, 8, 9]
Enumerable.Range(1, 0)
//==> []
(1..5).to_a
#==> [1, 2, 3, 4, 5]
(5..9).to_a
#==> [5, 6, 7, 8, 9]
(1..0).to_a
#==> []
Repeat
Enumerable.Repeat('o', 3)
//==> ['o', 'o', 'o'] (lazy)
Enumerable.Repeat('o', 0)
//==> [] (lazy)
// ?
//
// ?
//
['o'].cycle(3).to_a
#==> ['o', 'o', 'o'] (lazy)
['o'].cycle(0).to_a
#==> [] (lazy)
['o'] * 3
#==> ['o', 'o', 'o'] (eager)
['o'] * 0
#==> [] (eager)
Reverse
Enumerable.Range(1, 5).Reverse()
//==> [5, 4, 3, 2, 1]
(1..5).to_a.reverse()
#==> [5, 4, 3, 2, 1]
Select
Enumerable.Range(1, 5)
.Select(n => n * n)
//==> [1, 4, 9, 16, 25]

Enumerable.Range(1, 5)
.Select((n, i) => n * n + "/" + i)
//==> ["1/0", "4/1", "9/2", "16/3", "25/4"]
(1..5).
map {|n| n * n}
#==> [1, 4, 9, 16, 25]

(1..5).
each_with_index.map {|n, i| "#{n * n}/#{i}"}
#==> ["1/0", "4/1", "9/2", "16/3", "25/4"]
SelectMany
Enumerable.Range(1, 3)
.SelectMany(n => Enumerable.Repeat(n, n))
//==> [1, 2, 2, 3, 3, 3]
Enumerable.Range(1, 3)
.SelectMany((n, i) =>
            Enumerable.Repeat(n * i, n))
//==> [0, 2, 2, 6, 6, 6]
(1..3).
flat_map {|n| [n] * n}
#==> [1, 2, 2, 3, 3, 3]
(1..3).
each_with_index.
flat_map {|n, i| [n * i] * n}
#==> [0, 2, 2, 6, 6, 6]
SequenceEqual
var xs = Enumerable.Range(1, 3);
var ys = Enumerable.Range(1, 3);
xs == ys              //==> false
xs.SequenceEqual(ys)  //==> true

// ?
//
//
xs = (1..3)
ys = (1..3)
xs.equal?(ys)       #==> false
xs.to_a == ys.to_a  #==> true

xs == ys            #==> true
# Rangeに限って言えば==でも構わないが、
# 各シーケンスを列挙した方がSequenceEqualに近い。
Single
new int[] {2      }.Single()
//==> 2
new int[] {2, 3   }.Single()
//==> InvalidOperationException
new int[] {       }.Single()
//==> InvalidOperationException

new int[] {2      }.Single(n => n % 2 == 0)
//==> 2
new int[] {2, 3   }.Single(n => n % 2 == 0)
//==> 2
new int[] {2, 3, 4}.Single(n => n % 2 == 0)
//==> InvalidOperationException
new int[] {       }.Single(n => n % 2 == 0)
//==> InvalidOperationException
[2      ].one? {true}
#==> true
[2, 3   ].one? {true}
#==> true
[       ].one? {true}
#==> false

[2      ].one? {|n| n % 2 == 0}
#==> true
[2, 3   ].one? {|n| n % 2 == 0}
#==> true
[2, 3, 4].one? {|n| n % 2 == 0}
#==> false
[       ].one? {|n| n % 2 == 0}
#==> false
SingleOrDefault
new int[] {2}
.SingleOrDefault()
//==> 2
new int[] {2, 3}
.SingleOrDefault()
//==> 0
new int[] {}
.SingleOrDefault()
//==> 0

new int[] {2}
.SingleOrDefault(n => n % 2 == 0)
//==> 2
new int[] {2, 3}
.SingleOrDefault(n => n % 2 == 0)
//==> 2
new int[] {2, 3, 4}
.SingleOrDefault(n => n % 2 == 0)
//==> 0
new int[] {}
.SingleOrDefault(n => n % 2 == 0)
//==> 0
[2].
one? {true}
#==> true
[2, 3].
one? {true}
#==> true
[].
one? {true}
#==> false

[2].
one? {|n| n % 2 == 0}
#==> true
[2, 3].
one? {|n| n % 2 == 0}
#==> true
[2, 3, 4].
one? {|n| n % 2 == 0}
#==> false
[].
one? {|n| n % 2 == 0}
#==> false
Skip
"gochiusa".Skip(5)
//==> ['u', 's', 'a']
"gochiusa".Skip(0)
//==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
"gochiusa".Skip(-1)
//==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
"gochiusa".Skip(99)
//==> []
"gochiusa".each_char.drop(5)
#==> ['u', 's', 'a']
"gochiusa".each_char.drop(0)
#==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
"gochiusa".each_char.drop(-1)
#==> ArgumentError
"gochiusa".each_char.drop(99)
#==> []
SkipWhile
"gochiusa".SkipWhile(c => c != 'h')
//==> ['h', 'i', 'u', 's', 'a']
"gochiusa".SkipWhile(c => false)
//==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
"gochiusa".SkipWhile(c => true)
//==> []
"gochiusa".each_char.drop_while {|c| c != 'h'}
#==> ['h', 'i', 'u', 's', 'a']
"gochiusa".each_char.drop_while {|c| false}
#==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
"gochiusa".each_char.drop_while {|c| true}
#==> []
Sum
new int[] {}.Sum()
//==> 0
new int[] {1, 2, 3}.Sum()
//==> 6
new int?[] {1, null, 3}.Sum()
//==> 4

new [] {"cocoa", "chino", "rize"}
.Sum(s => s.Length)
//==> 14
new [] {"cocoa", "chino", "rize"}
.Sum(s => s != "cocoa" ? (int?)s.Length : null)
//==> 9
//
[].inject(:+)
#==> nil
[1, 2, 3].inject(:+)
#==> 6
[1, nil, 3].inject(:+)
#==> TypeError

["cocoa", "chino", "rize"]
.map {|s| s.length}.inject(:+)
#==> 14
["cocoa", "chino", "rize"]
.map {|s| s != "cocoa" ? s.length : nil}
.inject(:+)
#==> TypeError
Take
"gochiusa".Take(5)
//==> ['g', 'o', 'c', 'h', 'i']
"gochiusa".Take(0)
//==> []
"gochiusa".Take(-1)
//==> []
"gochiusa".Take(99)
//==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
"gochiusa".each_char.take(5)
#==> ['g', 'o', 'c', 'h', 'i']
"gochiusa".each_char.take(0)
#==> []
"gochiusa".each_char.take(-1)
#==> ArgumentError
"gochiusa".each_char.take(99)
#==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
TakeWhile
"gochiusa".TakeWhile(c => c != 'h')
//==> ['g', 'o', 'c']
"gochiusa".TakeWhile(c => false)
//==> []
"gochiusa".TakeWhile(c => true)
//==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
"gochiusa".each_char.take_while {|c| c != 'h'}
#==> ['g', 'o', 'c']
"gochiusa".each_char.take_while {|c| false}
#==> []
"gochiusa".each_char.take_while {|c| true}
#==> ['g', 'o', 'c', 'h', 'i', 'u', 's', 'a']
ThenBy
new [] {"cocoa", "chino", "rize"}
.OrderBy(s => s.Length)
.ThenBy(s => s)
//==> ["rize", "chino", "cocoa"]
%w(chino cocoa rize).
sort_by {|s| [s.length, s]}

#==> ["rize", "chino", "cocoa"]
ThenByDescending
new [] {"cocoa", "chino", "rize"}
.OrderBy(s => s.Length)
.ThenByDescending(s => s)
//==> ["rize", "cocoa", "chino"]
# TODO: ???
ToArray
Enumerable.Range(5, 5).ToArray()
//==> [5, 6, 7, 8, 9] (int[])
(5..9).to_a
#==> [5, 6, 7, 8, 9]
ToDictionary
new [] {"cocoa", "chino", "rize"}
.ToDictionary(s => s, s => s.Length)
//==> {"cocoa": 5, "chino": 5, "rize": 4}

new [] {"cocoa", "chino", "rize"}
.ToDictionary(s => s[0], s => s.Length)
//==> ArgumentException
// (keySelector produces
//  duplicate keys for two elements.)
%w(cocoa chino rize).
map {|s| [s, s.length]}.to_h
#==> {"cocoa" => 5, "chino" => 5, "rize" => 4}

%w(cocoa chino rize).
map {|s| [s[0..0], s]}.to_h
#==> {"c" => "cocoa", "r" => "rize"} or
#    {"c" => "chino", "r" => "rize"}
#
ToList
Enumerable.Range(5, 5).ToList()
//==> [5, 6, 7, 8, 9] (List<int>)
(5..9).to_a
#==> [5, 6, 7, 8, 9]
ToLookup
var l = new [] {"cocoa", "chino", "rize"}
  .ToLookup(s => s[0], s => s);
l["c"]  //==> ["cocoa", "chino"]
l["r"]  //==> ["rize"]
l["z"]  //==> []
l = %w(cocoa chino rize).
  group_by {|s| s[0..0]}
l["c"]  #==> ["cocoa", "chino"]
l["r"]  #==> ["rize"]
l["z"]  #==> nil
Union
var xs = new [] {1, 2, 3, 4, 5};
var ys = new [] {2, 4, 6, 8, 0};
xs.Union(ys)
//==> [1, 2, 3, 4, 5, 6, 8, 0]
xs = [1, 2, 3, 4, 5]
ys = [2, 4, 6, 8, 0]
xs | ys
#==> [1, 2, 3, 4, 5, 6, 8, 0]
Where
var ss = new [] {"cocoa", "chino", "rize"};
ss.Where(s => s.StartsWith("c"))
//==> ["cocoa", "chino"]
ss.Where((_, i) => i % 2 == 0)
//==> ["cocoa", "rize"]
//
ss = %w(cocoa chino rize)
ss.find_all {|s| s.start_with?('c')}
#==> ["cocoa", "chino"]
ss.each_with_index
.find_all {|_, i| i % 2 == 0}.map {|s, _| s}
#==> ["cocoa", "rize"]
Zip
var ss = new [] {"cocoa", "chino", "rize"};
var ns = Enumerable.Range(1, 100);
ss.Zip(ns, (s, n) => n + ":" + s)
//==> ["1:cocoa", "2:chino", "3:rize"]
ss = %w(cocoa chino rize)
ns = (1..100)
ss.zip(ns).map {|x| "#{x[1]}:#{x[0]}"}
#==> ["1:cocoa", "2:chino", "3:rize"]