challenge 全ての組み合わせ

2個以上のリストlist1, list2, list3...が与えられたときに、 その複数個のリストの中の要素を一つずつとりだして組にする方法の全通りのリストを返すコードを書いてください。

Pythonで表現すると下のようになります。

>>> c = CrossProduct([1,2,3,4], "abc")
>>> list(c.all())
[[1, 'a'], [1, 'b'], [1, 'c'], [2, 'a'],
 [2, 'b'], [2, 'c'], [3, 'a'], [3, 'b'],
 [3, 'c'], [4, 'a'], [4, 'b'], [4, 'c']]

>>> c = CrossProduct([0, 1], "ab", ["Foo", "Bar"])
>>> list(c.all())
[[0, 'a', 'Foo'], [0, 'a', 'Bar'], [0, 'b', 'Foo'], [0, 'b', 'Bar']
 [1, 'a', 'Foo'], [1, 'a', 'Bar'], [1, 'b', 'Foo'], [1, 'b', 'Bar']]
順番はこの通りでなくても構いません。返すものはリストと書きましたが、 なんらかの「一度に全部をメモリ上に作成しないリスト状のモノ」がある言語ではそちらを使う方がおすすめです。 数値や文字列を一つのリストに混在させるのがやっかいな言語では整数のリストに限定しても構いません。

このお題はZIGOROuさんとのやりとりにヒントを得て作りました。 (しまった、先にブログで公開されてしまった→Yet Another Hackadelic - 直積の導出と考えうる全ての値を網羅したハッシュの生成)

追記:サンプル出力が間違っていたのでoceanさんの解答を使って出力し直しました。

Posted feedbacks - Nested

Flatten Hidden
ただ全ての組み合わせを得るだけなら、Gaucheだとライブラリ関数cartesian-productで
済んでしまいます:

gosh> (use util.combinations)
#<undef>
gosh> (write (cartesian-product '((1 2 3 4) (#\a #\b #\c) (a b c))))
((1 #\a a) (1 #\a b) (1 #\a c) (1 #\b a) (1 #\b b) (1 #\b c) (1 #\c a) (1 #\c b) (1 #\c c) (2 #\a a) (2 #\a b) (2 #\a c) (2 #\b a) (2 #\b b) (2 #\b c) (2 #\c a) (2 #\c b) (2 #\c c) (3 #\a a) (3 #\a b) (3 #\a c) (3 #\b a) (3 #\b b) (3 #\b c) (3 #\c a) (3 #\c b) (3 #\c c) (4 #\a a) (4 #\a b) (4 #\a c) (4 #\b a) (4 #\b b) (4 #\b c) (4 #\c a) (4 #\c b) (4 #\c c))#<undef>

これではつまらないので、ジェネレータにしてみました。
実行例:

gosh> (define z (cross-products-gen '((1 2) (3 4 5))))
z
gosh> (z)
(1 3)
gosh> (z)
(1 4)
gosh> (z)
(1 5)
gosh> (z)
(2 3)
gosh> (z)
(2 4)
gosh> (z)
(2 5)
gosh> (z)
#f

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(use util.combinations)

(define (cross-products-gen lists)
  (let ((restart #f)
        (return #f))
    (lambda ()
      (let/cc k
        (set! return k)
        (cond (restart (restart #f))
              (else (cartesian-product-for-each
                     (lambda (p) (let/cc k (set! restart k) (return p)))
                     lists)
                    #f))))))
普通に再帰で。(二番目の例の答えが違うけど、これでいいんですよね?)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def cross_product(*a):
    stack = []
    def traverse(i):
        if i == len(a):
            yield stack[:]
        else:
            for x in a[i]:
                stack.append(x)
                for the_stack in traverse(i + 1):
                    yield the_stack
                stack.pop()
    return traverse(0)

def print_cross_product(*a):
    for b in cross_product(*a):
        print b
    print

def main():
    print_cross_product([1,2,3,4], "abc")
    print_cross_product([0, 1], "ab", ["Foo", "Bar"])

if __name__ == '__main__':
    main()
サンプル出力間違い2連続orz
すぐ直します…orz
なるほど、stackって何に使うのかと思ったのですが
stackに入れたり出したりして値のペアを作り
コピーして返しているんですね。

速いのかな、と調べてみたのですが[1,2,3,4], "abc"のデータでは
下の書き方の方が40%くらい速かったです。
(1万回繰り返しで0.34と0.20)
# いまいち納得のいかない結果…
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def cross_product2(*args):
	if not args:
		yield []
	elif len(args) == 1:
		for x in args[0]:
			yield [x]
	else:
		for x in args[0]:
			for y in cross_product2(*args[1:]):
				yield [x] + y

引数をrange(10), range(10)と増やしてみると、 元のが2.00秒、新しいのが1.05秒と差が広がりました。

逆にrange(3), range(3), range(3), range(3)と深くしてみたところ 元のが2.21、新しいのが2.11と僅差になりました。

appendとpopを繰り返すコストが、リストの加算で毎回新しく作ってしまうコストに比べてあんまり安くないんでしょう… なのであまり再帰呼び出しが深くない(掛け合わせる要素が多くない)場合には作ってしまう方が速い、と。

ちなみに下のように無駄な処理をループの外でやるようにしたところ0.16秒と92%の高速化になってしまいました。 もちろんそのぶんメモリは食いますが…。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def cross_product2(*args):
	if not args:
		yield []
	elif len(args) == 1:
		for x in args[0]:
			yield [x]
	else:
		tails = cross_product2(*args[1:])
		for x in args[0]:
			head = [x]
			for tail in tails:
				yield head + tail
速度のことはあまり考えてなかったんですが、結構差がつきますね。stackでは配列をひとつしか使わないので、理屈では省メモリのはずですが、それより遅いデメリットの方が問題そうですね。

stack.pop() より del stack[-1] の方が速いというのも昔経験したのですが、(stack[-1]の値自体を必要としない場合)やっぱり pop の関数をメソッドテーブルから検索するのが馬鹿にならないのかな。

	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Collections;
using System.Collections.Generic;
class Program
{
  static void Main()
  {
    int[] ii = { 0, 1 };
    string[] ss = { "Foo", "Bar" };
    List<List<object>> lst = CrossProduct(ii, "ab".ToCharArray(), ss);
    foreach (List<object> l in lst)
    {
      foreach (object o in l) Console.Write(o + ", ");
      Console.WriteLine();
    }
  }
  static List<List<object>> CrossProduct(params IList[] lsts)
  {
    int[] pos = new int[lsts.Length];
    List<List<object>> lst = new List<List<object>>();
    int n1 = lsts.Length - 1;
    while (true)
    {
      List<object> l = new List<object>();
      for (int i = 0; i < lsts.Length; i++)l.Add(lsts[i][pos[i]]);
      lst.Add(l);
      int k = n1;
      while (k >= 0 && pos[k] == lsts[k].Count - 1) pos[k--] = 0;
      if (k < 0) break;
      ++pos[k];
    }
    return lst;
  }
}
もう1つおまけ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
static List<List<object>> CrossProduct(params IList[] lsts)
{
  List<List<object>> lst = new List<List<object>>();
  lst.Add(new List<object>());
  foreach (IList il in lsts)
  {
    List<List<object>> l = new List<List<object>>();
    foreach (List<object> ls0 in lst)
    {
      foreach (object o in il)
      {
        List<object> ls = new List<object>(ls0);
        ls.Add(o);
        l.Add(ls);
      }
    }
    lst = l;
  }
  return lst;
}
イテレータその1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/// <summary>
/// 2つのIEnumerableを組合わせる
/// </summary>
public sealed class MulEnumerable<T, U> : IEnumerable<Pair<T, U>>
{
  private IEnumerable<T> ie1;
  private IEnumerable<U> ie2;
  /// <summary>コンストラクタ</summary>
  public MulEnumerable(IEnumerable<T> e1, IEnumerable<U> e2)
  {
    ie1 = e1;
    ie2 = e2;
  }
  /// <summary></summary>
  public IEnumerator<Pair<T, U>> GetEnumerator()
  {
    foreach (T t1 in ie1) foreach (U t2 in ie2)
        yield return new Pair<T, U>(t1, t2);
  }
  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    return ((IEnumerable<Pair<T, U>>)this).GetEnumerator();
  }
}
イテレータその2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/// <summary>
/// 任意個のIEnumerableを組合わせる
/// </summary>
public sealed class MulEnumerable : IEnumerable<object[]>
{
  private System.Collections.IEnumerable[] ie;
  /// <summary>コンストラクタ</summary>
  public MulEnumerable(params System.Collections.IEnumerable[] e)
  {
    ie = e;
  }
  /// <summary></summary>
  public IEnumerator<object[]> GetEnumerator()
  {
    System.Collections.IEnumerator[] en = new System.Collections.IEnumerator[ie.Length];
    for (int i = 0; i < ie.Length; ++i)
      (en[i] = ie[i].GetEnumerator()).MoveNext();
    int n1 = ie.Length - 1;
    while (true)
    {
      object[] l = new object[ie.Length];
      for (int i = 0; i < ie.Length; ++i) l[i] = en[i].Current;
      yield return l;
      int k = n1;
      while (k >= 0 && !en[k].MoveNext())
      {
        en[k] = ie[k].GetEnumerator();
        en[k].MoveNext();
        --k;
      }
      if (k < 0) break;
    }
  }
  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    return ((IEnumerable<object[]>)this).GetEnumerator();
  }
}
結果は(見易さのために改行を足しましたが)こんな感じです。

scala> crossproduct(List(1,2,3), List(4,5,6))
res14: List[List[Int]] = 
  List(List(1, 4), List(1, 5), List(1, 6), 
       List(2, 4), List(2, 5), List(2, 6), 
       List(3, 4), List(3, 5), List(3, 6))

scala> crossproduct(List(1,2),List(4,5),List(6,7))
res15: List[List[Int]] = 
  List(List(1, 4, 6), List(1, 4, 7), List(1, 5, 6), List(1, 5, 7), 
       List(2, 4, 6), List(2, 4, 7), List(2, 5, 6), List(2, 5, 7))
1
2
3
4
5
6
def crossproduct[T](xss: List[T]*) = {
  var ps = List(List[T]())
  for (xs <- xss.reverse)
    ps = for (i <- xs; j <- ps) yield i::j
  ps
}
Squeak Smalltalk で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
| input results gen |
input := #((0 1) 'ab' (Foo Bar)).
results := OrderedCollection new.
gen := input reversed
    inject: [:arr | results add: arr]
    into: [:block :colln |
        [:arr | colln do: [:ea | block value: arr, {ea}]] fixTemps].
gen value: #().
^results asArray

"=> #((0 $a Foo) (0 $a Bar) (0 $b Foo) (0 $b Bar)
   (1 $a Foo) (1 $a Bar) (1 $b Foo) (1 $b Bar)) "
ether さんの #2152 の簡潔さに感心しつつ、それをヒントにして同様のことを Squeak Smalltalk で。
1
2
3
4
| input |
input := #((0 1) 'ab' (foo bar)).
^input reversed inject: #(()) into: [:arrs :colln |
    colln inject: #() into: [:rs :elem | rs, (arrs collect: [:arr | {elem}, arr])]]
ずいぶんカバレッジ下がりました。
勝手にHaskellに対抗意識を燃やしています。
1
2
3
crossp([],[]).
crossp([X|Xs],[I|Is]):-member(I,X),crossp(Xs,Is).
:-findall(X,crossp([[1,2,3],[uno,due,tre],[un,doux,trois]],X),Xs),writeln(Xs).
芸がない。そのくせ型という制約がある。
1
crossProduct = sequence :: [[a]] -> [[a]]
リストの数固定して、Dynamicを使うというのを書いてみました。
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
import Data.Dynamic

crossProduct2 :: (Typeable a, Typeable b)
              => [a] -> [b] -> [(a,b)]
crossProduct2 ps qs 
 = map tuple 
 $ sequence 
 $ [ map toDyn ps
   , map toDyn qs
   ]
 where tuple [p,q]
        = ( fromDyn p undefined
          , fromDyn q undefined
          )

crossProduct3 :: (Typeable a, Typeable b, Typeable c)
              => [a] -> [b] -> [c] -> [(a,b,c)]
crossProduct3 ps qs rs
 = map tuple 
 $ sequence 
 $ [ map toDyn ps
   , map toDyn qs
   , map toDyn rs
   ]
 where tuple [p,q,r]
        = ( fromDyn p undefined
          , fromDyn q undefined
          , fromDyn r undefined
          )

crossProduct4 :: (Typeable a, Typeable b, Typeable c, Typeable d)
              => [a] -> [b] -> [c] -> [d] -> [(a,b,c,d)]
crossProduct4 ps qs rs ss
 = map tuple 
 $ sequence 
 $ [ map toDyn ps
   , map toDyn qs
   , map toDyn rs
   , map toDyn ss
   ]
 where tuple [p,q,r,s]
        = ( fromDyn p undefined
          , fromDyn q undefined
          , fromDyn r undefined
          , fromDyn s undefined
          )

crossProduct5 :: (Typeable a, Typeable b, Typeable c, Typeable d, Typeable e)
              => [a] -> [b] -> [c] -> [d] -> [e] -> [(a,b,c,d,e)]
crossProduct5 ps qs rs ss ts
 = map tuple 
 $ sequence 
 $ [ map toDyn ps
   , map toDyn qs
   , map toDyn rs
   , map toDyn ss
   , map toDyn ts
   ]
 where tuple [p,q,r,s,t]
        = ( fromDyn p undefined
          , fromDyn q undefined
          , fromDyn r undefined
          , fromDyn s undefined
          , fromDyn t undefined
          )

-- Test data

data RGB = Red | Green | Blue  deriving (Typeable,Bounded,Enum,Show)
data ENWS = East | North | West | South deriving (Typeable,Bounded,Enum,Show)

allItem :: (Bounded a, Enum a) => [a]
allItem = [minBound..maxBound]

test = crossProduct4 (allItem::[()]) (allItem::[Bool]) (allItem::[RGB]) (allItem::[ENWS])

{-
*Main> putStr $ unlines $ map show $ test
((),False,Red,East)
((),False,Red,North)
((),False,Red,West)
((),False,Red,South)
((),False,Green,East)
((),False,Green,North)
((),False,Green,West)
((),False,Green,South)
((),False,Blue,East)
((),False,Blue,North)
((),False,Blue,West)
((),False,Blue,South)
((),True,Red,East)
((),True,Red,North)
((),True,Red,West)
((),True,Red,South)
((),True,Green,East)
((),True,Green,North)
((),True,Green,West)
((),True,Green,South)
((),True,Blue,East)
((),True,Blue,North)
((),True,Blue,West)
((),True,Blue,South)
-}
直積はcross productじゃなくてdirect productだったと思います。
1
2
3
4
5
#light
open List
let rec directProduct = function
    | [] -> [[]]
    | ls::lss -> [for l in ls for rest in (directProduct lss) -> l::rest]
fiber でジェネレータにしました。

「(...)」 というのは可変長引数をあらわすオブジェクトです。でも、コレ使うのなんかクラッシュする。。。

あと、配列の分配がきれいにかけないです。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
CrossProduct: class {
    - _list: [];

    initialize: method(...) {
        (...).each_ordered_arg {
            // ここのコメントを削除するとなぜかクラッシュする^^
            if (it is String) {
                _list.push_back(it.split("").to_a);
            } else {
                _list.push_back(it);
            }
        }
    }

    all: method fiber {
        (fun (acc, rest) {
            if (rest.empty()) {
                yield acc;
            } else {
                x, xs: rest[0], rest.slice(1, rest.length);
                x {
                    callee(acc ~ [it], xs);
                }
            }
        })([], _list);
    }
}

c: CrossProduct([0, 1], "ab", ["Foo", "Bar"]);
c.all {
    it.p;
}
ちょうど、一つ前のお題で任意の個数からなるBool値のタプルの
組み合わせを生成するのに、同じようなコードを書きました。

余談ですが「コード:」のテキストボックスのフォントが、等幅だとうれしいのですが。
1
2
3
4
5
6
7
def CP(*l):
  def f(l):
    return ([a] + x for a in l[0] for x in f(l[1:])) if len(l) > 1 else ([a] for a in l[0])
  return f(l)

print CP([1,2,3,4], "abc")
print CP([0, 1], "ab", ["Foo", "Bar"])
おっと、ジェネレータは単にprintしてもダメだった。

ちょっとだけ修正。
1
2
3
4
5
6
7
def CP(*l):
  def f(l):
    return ([a] + x for a in l[0] for x in f(l[1:])) if len(l) > 1 else ([a] for a in l[0])
  return f(l)

print list(CP([1,2,3,4], "abc"))
print list(CP([0, 1], "ab", ["Foo", "Bar"]))
単純に再帰で
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.*;

public class Sample {
    private static void comb(ArrayList<Object> ans, List[] lists, int count, 
                            List<List> result) {
        if (count >= lists.length) {
            result.add(ans);
            return;
        }
        for (Object ele : lists[count]) {
            ArrayList<Object> a = new ArrayList<Object>(ans);
            a.add(ele);
            comb(a, lists, count + 1, result);
        }
    }

    public static List combination(List... lists) {
        List<List> result = new ArrayList<List>();
        comb(new ArrayList<Object>(), lists, 0, result);
        return result;
    }

    public static void main(String[] args) {
        System.out.println(combination(Arrays.asList(0, 1),
                                       Arrays.asList('a', 'b'),
                                       Arrays.asList("foo", "bar")));
    }
}
1行にしてみました。
1
2
3
4
5
6
7
def crossProduct(as:List[Any]*) = {
  as.reverse./:(List(List[Any]())){for(i <-_; j <-_) yield j::i}
}


println(crossProduct(List(1,2,3,4), List('a', 'b', 'c')))
println(crossProduct(List(0,1), List('a', 'b'), List("Foo", "Bar")))
引数の数(リストの数)を固定する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
crossProduct2 = concatMap . (. repeat) . zip
crossProduct3 = (. crossProduct2) . (.) . crossProduct2
crossProduct4 = (. crossProduct3) . (.) . (.) . crossProduct2
crossProduct5 = (. crossProduct4) . (.) . (.) . (.) . crossProduct2
crossProduct6 = (. crossProduct5) . (.) . (.) . (.) . (.) . crossProduct2
crossProduct7 = (. crossProduct6) . (.) . (.) . (.) . (.) . (.) . crossProduct2

-- Test data

data RGB = Red | Green | Blue  deriving (Bounded,Enum, Show)
data ENWS = East | North | West | South deriving (Bounded,Enum, Show)

allItems :: (Bounded a, Enum a) => [a]
allItems = [minBound..maxBound]

test = crossProduct4 (allItems::[()]) (allItems::[Bool]) (allItems::[RGB]) (allItems::[ENWS])

{-
*Main> test
[((),(False,(Red,East)))
,((),(True,(Red,East)))
,((),(False,(Green,East)))
,((),(True,(Green,East)))
,((),(False,(Blue,East)))
,((),(True,(Blue,East)))
,((),(False,(Red,North)))
,((),(True,(Red,North)))
,((),(False,(Green,North)))
,((),(True,(Green,North)))
,((),(False,(Blue,North)))
,((),(True,(Blue,North)))
,((),(False,(Red,West)))
,((),(True,(Red,West)))
,((),(False,(Green,West)))
,((),(True,(Green,West)))
,((),(False,(Blue,West)))
,((),(True,(Blue,West)))
,((),(False,(Red,South)))
,((),(True,(Red,South)))
,((),(False,(Green,South)))
,((),(True,(Green,South)))
,((),(False,(Blue,South)))
,((),(True,(Blue,South)))]
-}
loop萌え~
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(defun cross-product (&rest sequences)
  (labels ((rec (lists)
             (if (null (cdr lists))
                 (loop for a in (car lists) collect (list a))
                 (loop for x in (rec (cdr lists)) appending
                      (loop for a in (car lists) collect
                           `(,a ,@x))))))
    (rec (loop for arg in sequences collect (concatenate 'list arg)))))

(cross-product '(1 2 3 4) "abc")           ; => ((1 #\a) (2 #\a) (3 #\a) (4 #\a) (1 #\b) (2 #\b) (3 #\b) (4 #\b) (1 #\c) (2 #\c) (3 #\c) (4 #\c))
(cross-product #(0 1) "ab" '("Foo" "Bar")) ; => ((0 #\a "Foo") (1 #\a "Foo") (0 #\b "Foo") (1 #\b "Foo") (0 #\a "Bar") (1 #\a "Bar") (0 #\b "Bar") (1 #\b "Bar"))
配列でお茶を濁すと…
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def cross_product(*arrays)
  if arrays.length == 1
    arrays.first.map{|x| [x]}
  else
    cross_product(*arrays[1..-1]).inject([]) {|result, x|
      result.concat arrays.first.map{|a| [a, *x] }
    }
  end
end

cross_product [1,2,3,4], %w[a b c]  # => [[1, "a"], [2, "a"], [3, "a"], [4, "a"], [1, "b"], [2, "b"], [3, "b"], [4, "b"], [1, "c"], [2, "c"], [3, "c"], [4, "c"]]
cross_product [0,1], %w[a b], ["Foo", "Bar"] # => [[0, "a", "Foo"], [1, "a", "Foo"], [0, "b", "Foo"], [1, "b", "Foo"], [0, "a", "Bar"], [1, "a", "Bar"], [0, "b", "Bar"], [1, "b", "Bar"]]
文字列を1文字ずつ無理矢理取り出すにはこんな細工をする。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Array
  alias :_by_element :to_a
end

class String
  def _by_element
    split(//)
  end
end

def cross_product(*sequences)
  if sequences.length == 1
    sequences.first._by_element.map{|x| [x]}
  else
    cross_product(*sequences[1..-1]).inject([]) {|result, x|
      result.concat sequences.first._by_element.map{|a| [a, *x] }
    }
  end
end

cross_product [1,2,3,4], %w[a b c]  # => [[1, "a"], [2, "a"], [3, "a"], [4, "a"], [1, "b"], [2, "b"], [3,