ORACLE TECHNOLOGY NETWORK
 
 
   

Oracle Technology Network (OTN) Japan - 掲示板 » テクノロジー » プログラミング一般

スレッド: 組合せ(重複なし)

このスレッドに返信する このスレッドに返信する スレッド一覧へ スレッド一覧へ

Permlink 返信数: 10 - 最新投稿 : 2004/08/03 17:33 最新投稿者: ushitaki - スレッド表示形式:
ushitaki

投稿数: 7,079
登録日時: 98/10/30


組合せ(重複なし)
投稿時刻: 2004/07/31 0:57
  このスレッドに返信します… 返信

Hi Oracler and SQL Puzzler!

初心者のコーナー での connect by の話で
懸賞問題を思いつきました。

高校とか(もしかしたら中学)の数学で集合論とかの話で
「順列」とか「組合せ」なる用語がありました。

今回はこの組合せ(結果&件数)を求めたいと思います。


create table comb(i number primary key);

-- テスト用 insert は各自で工夫して下さい。
-- 問題提出者が煮詰めていないとも言う...
-- 昨年回答者 0 になってしまった
-- ホテルの空室予約問題と同様になったりして。


(問題1)
データ条件として重複を許しません。
function print_comb(LEV in number) return varchar2;
LEV : 組合せとして何個選ぶかを指定します。
return : カンマ編集なりなんなりで若い順に返してください。

(問題2)
function count_comb(LEV in number) return number;
LEV : 組合せとして何個選ぶかを指定します。
return : 組合せ件数を返してください(実はわながあります)。



hoge

投稿数: 11,812
登録日時: 99/03/15


RE:組合せ(重複なし)
投稿時刻: 2004/07/31 11:46   ushitaki さんへの返信です。 ushitaki さんへの返信です。
  このスレッドに返信します… 返信

>(問題1)
>データ条件として重複を許しません。
>function print_comb(LEV in number) return varchar2;
>LEV : 組合せとして何個選ぶかを指定します。
>return : カンマ編集なりなんなりで若い順に返してください。

組み合わせ・・夏休みの宿題みたいですね。
あまり自信ないですが・・・^^;
環境は9iR2です。

CREATE OR REPLACE function print_comb(LEV in number)
return varchar2
is
cursor cura is select lv,a.i1,a.i2 from
(select level lv,a.i i1,b.i i2 from comb a,comb b
where a.i < b.i
connect by prior b.i = a.i and level < lev) a
where exists
(select null from(select level lv,a.i i1,b.i i2 from comb a,comb b
where a.i < b.i
connect by prior b.i = a.i and level < lev) b
where (a.lv < b.lv and a.i2 = b.i1) or a.lv = lev-1);
cursor curb is select i from comb order by i;
val varchar2(4000);
wk varchar2(4000);
type hoge is table of varchar2(100) index by binary_integer;
arr_hoge hoge;
begin
dbms_output.enable(1000000);
if lev = 1 then
val := '';
for rec in curb loop
if val is not null then val := val||','; end if;
val := val||rec.i;
end loop;
else
for rec in cura loop
arr_hoge(rec.lv) := rec.i1;
if rec.lv = lev - 1 then
arr_hoge(rec.lv+1) := rec.i2;
wk := '';
for i in 1..lev loop
if i > 1 then wk := wk||','; end if;
wk := wk||arr_hoge(i);
end loop;
dbms_output.put_line(wk);
val := val||wk||' ';
end if;
end loop;
end if;
return val;
end;
/

>(問題2)
>function count_comb(LEV in number) return number;
>LEV : 組合せとして何個選ぶかを指定します。
>return : 組合せ件数を返してください(実はわながあります)。

わなですか?・・・どんなわなが・・・lev=1のときでしょうか・・・
問題1のfunnction内での処理数もしくは以下のSQLではだめですか?

select max(cnt) from(
select count(*) cnt from
(select level lv,a.i i1,b.i i2 from comb a,comb b
where a.i < b.i
connect by prior b.i = a.i and level < lev) a
where lv = lev-1
union all
select count(*) from comb where 1 = lev);



ushitaki

投稿数: 7,079
登録日時: 98/10/30


RE[1]:組合せ(重複なし)
投稿時刻: 2004/07/31 13:06   hoge さんへの返信です。 hoge さんへの返信です。
  このスレッドに返信します… 返信

>>(問題2)
>>function count_comb(LEV in number) return number;
>>LEV : 組合せとして何個選ぶかを指定します。
>>return : 組合せ件数を返してください(実はわながあります)。
>
>わなですか?・・・どんなわなが・・・lev=1のときでしょうか・・・

ズルイと言われるかも知れませんが...
全件の count(*) を一回行えば求められるのです。

(問題1)に縛られると違うアプローチの簡単な方法を見逃します。
でも(問題4)は全件の count(*) 一回では求められません。


hoge

投稿数: 11,812
登録日時: 99/03/15


RE[2]:組合せ(重複なし)
投稿時刻: 2004/07/31 13:37   ushitaki さんへの返信です。 ushitaki さんへの返信です。
  このスレッドに返信します… 返信

>全件の count(*) を一回行えば求められるのです。

なるほど・・・あっさり縛られていました。^^;

select lv+1,count(*) from(
select level lv,a.i i1,b.i i2 from comb a,comb b
where a.i < b.i
connect by prior b.i = a.i)
group by lv;

上記で各lev毎の組み合わせが求められそうですね。
組み合わせですのでlev=1の値はlev=max-1の値と同じ。
そういうことでしょうか?



ushitaki

投稿数: 7,079
登録日時: 98/10/30


RE[3]:組合せ(重複なし)
投稿時刻: 2004/07/31 15:02   hoge さんへの返信です。 hoge さんへの返信です。
  このスレッドに返信します… 返信

>>全件の count(*) を一回行えば求められるのです。

一番ずるいのは数学の公式を使うと言う事でして
connect by も要りません。

connect by を使う例では count だけが欲しいので
下記となります。Oracle7.3 で確認済み。

select count(*) from
(
select level lv,i from comb
connect by prior i < i
and level <= LEV
)
where lv = LEV
;

connect by で同じキーでの不等号条件と
level 関数で縛りを入れているのがポイント。
(LEV 以下の組合せで終わるものを含む)

あれ? そう言えばテーブルは下記ですよ。

create table comb(i number primary key);


hoge

投稿数: 11,812
登録日時: 99/03/15


RE[4]:組合せ(重複なし)
投稿時刻: 2004/07/31 16:09   ushitaki さんへの返信です。 ushitaki さんへの返信です。
  このスレッドに返信します… 返信

>一番ずるいのは数学の公式を使うと言う事でして
>connect by も要りません。

なるほど・・・^^;

>select count(*) from
>(
>select level lv,i from comb
>connect by prior i < i
> and level <= LEV
>)
>where lv = LEV
>;
>
>connect by で同じキーでの不等号条件と
>level 関数で縛りを入れているのがポイント。
>(LEV 以下の組合せで終わるものを含む)

#以下iには1から5が入っています。

SQL> select lv,count(*) from
2 (select level lv,i from comb
3 connect by prior i < i)
4 group by lv;

LV COUNT(*)
---------- ----------
1 5
2 10
3 10
4 5
5 1

おお・・なるほど・・これは思いつきませんでした。

>あれ? そう言えばテーブルは下記ですよ。
>create table comb(i number primary key);

一応上記テーブル使っているのですが自己結合する必要もなかったのですね。
というか自己結合してしまったのではまってしまったようです・・・
うーん・・まだ修行が足りませんね。



ushitaki

投稿数: 7,079
登録日時: 98/10/30


RE[5]:組合せ(重複なし)
投稿時刻: 2004/07/31 16:26   hoge さんへの返信です。 hoge さんへの返信です。
  このスレッドに返信します… 返信

問題1と問題2は脳エステ(*1)になりましたでしょうか?

問題3と問題4は厄介です。
connect by でループしないようにしないといけませんし
それでもさらに自分で distinct しなければならないので
Oracle7.3 レベルでは不可能かも。

しかしバージョン指定無しなので上位バージョンの機能は
使っても構いません(問題1と問題2も指定は無かったけど)。

(*1)
http://www.kyodo-tv.co.jp/ja/infovar/iq/

ushitaki

投稿数: 7,079
登録日時: 98/10/30


RE:組合せ(重複なし)
投稿時刻: 2004/07/31 13:22   ushitaki さんへの返信です。 ushitaki さんへの返信です。
  このスレッドに返信します… 返信

>(問題1)
>データ条件として重複を許しません。
>function print_comb(LEV in number) return varchar2;
>LEV : 組合せとして何個選ぶかを指定します。
>return : カンマ編集なりなんなりで若い順に返してください。

思いつきで書いたため変な仕様になってしまいました。
例えば 1,2,3,4,5,6,7 の7件のデータから4件の組合せを取るのなら
1,2,3,4|1,2,3,5|....|4,5,6,7 と言うように
2種類のセパレータ ',' と '|' とかを用いらないと区別できません。
(カンマ編集と書いたので組合せの間の方がカンマか...)

なお、若い順と言うのは、組合せの部分だけです。
組合せ間は気にしないで下さい。


12198 hoge さんの回答の場合は ' ' と ',' になっています。

ちなみに sys_connect_by_path と order sibiling by を使えば
Oracle9i 9.20 では、かなり楽になります。
Oracle9i 以上なら組合せ間も若い順にすることも可能です。



freak

投稿数: 534
登録日時: 02/02/22


RE[1]:組合せ(重複なし)
投稿時刻: 2004/08/03 15:32   ushitaki さんへの返信です。 ushitaki さんへの返信です。
  このスレッドに返信します… 返信


>ちなみに sys_connect_by_path と order sibiling by を使えば
>Oracle9i 9.20 では、かなり楽になります。

後だしジャンケンみたいで、ちょっと気が引けますが。。。

問題1

CREATE OR REPLACE function print_comb(LEV in number)
return varchar2
is
cursor cura is select Ans1 from (
select sys_connect_by_path(to_char(i,'fm09'),'|') || '|' Ans1,level LV
from comb connect by prior i < i and
level <= LEV
order siblings by i ) where LV = LEV;
val varchar2(2000);
begin
val := '';
for rec in cura loop
if val is not null then val := val || ','; end if;
val := val || rec.Ans1;
end loop;
return val;
end;
/
show errors

問題2

CREATE OR REPLACE function count_comb(LEV in number)
return number is
i number;
a number;
b number;
c number;
begin

a:=1; b:=1;
select count(*) into c from comb;

for i in (c-LEV+1)..c loop
a := a * i ;
end loop;
for i in 1..LEV loop
b := b * i ;
end loop;

return (a/b);
end;
/
show errors

# ずるいやり方です。 ^^;;


ushitaki

投稿数: 7,079
登録日時: 98/10/30


RE[2]:組合せ(重複なし)
投稿時刻: 2004/08/03 17:33   freak さんへの返信です。 freak さんへの返信です。
  このスレッドに返信します… 返信

問題2:ずるいやり方はループ回数をさらに減らせたりする。

CREATE OR REPLACE function count_comb(LEV in number)
return number is
n number;
r number;
C number;
begin
select count(*) into n from comb;
if ((lev < 0) or (lev > n)) then -- エラーパターン
return 0;
elsif (lev <= n/2) then -- nCr = nCn-r
r := lev;
else
r := n-lev;
end if;

C:=1;
for i in 1..r loop
C := C * n / i ; -- 絶対に割切れるのだ
n := n - 1;
end loop;
return C;
end;
/
show errors


ushitaki

投稿数: 7,079
登録日時: 98/10/30


RE:組合せ(重複なし)
投稿時刻: 2004/07/31 16:10   ushitaki さんへの返信です。 ushitaki さんへの返信です。
  このスレッドに返信します… 返信

Oracle 7.3 での回答例

begin
for i in 1..10 loop
insert into comp values(i);
end loop;
end;
/
commit;


create or replace function print_comb(LEV in number) return varchar2
is
cursor c_tree(LEV in number) is
select level lv,i
from comb
connect by level <= LEV
and prior i < i;
-- and ( (prior i<i)
-- or (prior i=i and prior rowid><rowid)); --問題3
p_sep1 varchar2(1);
p_sep2 varchar2(1);
p_ret varchar2(2000);
p_tmp varchar2(2000);
p_prev number;
p_pos number;
begin
p_sep1 := '/'; p_sep2 := ',';
p_ret := ''; p_tmp := '';
p_prev := 0;
for p_tree in c_tree(LEV) loop
if (p_tree.lv = 1) then
p_tmp := to_char(p_tree.i,'fm0999'); -- 4桁決打ち
else
if (p_tree.lv ><= p_prev) then
p_tmp := substrb(p_tmp,1, (p_tree.lv-1)*(4+1)-1);
--p_sep1でinstrbした方が柔軟性があって良い
end if;
p_tmp := p_tmp||p_sep1||to_char(p_tree.i,'fm0999');
end if;
-- dbms_output.put_line(p_tmp);
if (p_tree.lv = LEV) then
if (p_ret is null or p_ret = '') then
p_ret := p_tmp;
else
p_ret := p_ret||p_sep2||p_tmp;
end if;
end if;
p_prev := p_tree.lv;
end loop;
return p_ret;
end;
/
show error

declare c varchar2(2000);
begin
c := print_comb(3);
for i in 1..lengthb(c)/45+1 loop
dbms_output.put_line(substrb(c,(i-1)*45+1,45));
end loop;
end;
/

0001/0002/0003,0001/0002/0004,0001/0002/0005,
0001/0002/0006,0001/0002/0007,0001/0002/0008,
0001/0002/0009,0001/0002/0010,0001/0003/0004,
0001/0003/0005,0001/0003/0006,0001/0003/0007,
0001/0003/0008,0001/0003/0009,0001/0003/0010,
0001/0004/0005,0001/0004/0006,0001/0004/0007,
0001/0004/0008,0001/0004/0009,0001/0004/0010,
0001/0005/0006,0001/0005/0007,0001/0005/0008,
0001/0005/0009,0001/0005/0010,0001/0006/0007,
0001/0006/0008,0001/0006/0009,0001/0006/0010,
0001/0007/0008,0001/0007/0009,0001/0007/0010,
0001/0008/0009,0001/0008/0010,0001/0009/0010,
0002/0003/0004,0002/0003/0005,0002/0003/0006,
0002/0003/0007,0002/0003/0008,0002/0003/0009,
0002/0003/0010,0002/0004/0005,0002/0004/0006,
0002/0004/0007,0002/0004/0008,0002/0004/0009,
0002/0004/0010,0002/0005/0006,0002/0005/0007,
0002/0005/0008,0002/0005/0009,0002/0005/0010,
0002/0006/0007,0002/0006/0008,0002/0006/0009,
0002/0006/0010,0002/0007/0008,0002/0007/0009,
0002/0007/0010,0002/0008/0009,0002/0008/0010,
0002/0009/0010,0003/0004/0005,0003/0004/0006,
0003/0004/0007,0003/0004/0008,0003/0004/0009,
0003/0004/0010,0003/0005/0006,0003/0005/0007,
0003/0005/0008,0003/0005/0009,0003/0005/0010,
0003/0006/0007,0003/0006/0008,0003/0006/0009,
0003/0006/0010,0003/0007/0008,0003/0007/0009,
0003/0007/0010,0003/0008/0009,0003/0008/0010,
0003/0009/0010,0004/0005/0006,0004/0005/0007,
0004/0005/0008,0004/0005/0009,0004/0005/0010,
0004/0006/0007,0004/0006/0008,0004/0006/0009,
0004/0006/0010,0004/0007/0008,0004/0007/0009,
0004/0007/0010,0004/0008/0009,0004/0008/0010,
0004/0009/0010,0005/0006/0007,0005/0006/0008,
0005/0006/0009,0005/0006/0010,0005/0007/0008,
0005/0007/0009,0005/0007/0010,0005/0008/0009,
0005/0008/0010,0005/0009/0010,0006/0007/0008,
0006/0007/0009,0006/0007/0010,0006/0008/0009,
0006/0008/0010,0006/0009/0010,0007/0008/0009,
0007/0008/0010,0007/0009/0010,0008/0009/0010





ウェブサイトのご使用条件 | 個人情報保護基本方針/情報保護基本方針