- PR -

お手本になるようなソースコード

投稿者投稿内容
よねKEN
ぬし
会議室デビュー日: 2003/08/23
投稿数: 472
投稿日時: 2007-05-10 12:49
引用:

orzさんの書き込み (2007-05-10 11:51) より:
64000円を、
「10000円札6枚 + 1000円札4枚 = 10枚」
で支払うより
「8000円札8枚 = 8枚」
で払ったほうが少なく済む、と言うことだと思います。



フォローありがとうございます。
大きい金額から順に割った場合が最小枚数とは限らなくなるということですね。

自分なりに実業務を頭に思い浮かべて想像していたので、
経験から大きい金額から順に割るという発想しかありませんでした。

小型のATMなどの場合、極端な話、札の5000、2000、硬貨も500、50、5は
なくても運用ができるので、オプションとして扱われることが多く、
10000、1000、100、10、1などのメジャーな金種(無いと絶対に困る金種)
に比べて扱いが低かったりします。

なので、実業務なら8000円という金種はそもそも扱わないか、
5000円と同じ扱いをするだろうと思いました。


[ メッセージ編集済み 編集者: よねKEN 編集日時 2007-05-10 12:52 ]
ひろれい
ぬし
会議室デビュー日: 2006/03/02
投稿数: 486
お住まい・勤務地: 万博開催地
投稿日時: 2007-05-10 12:59
引用:

unibonさんの書き込み (2007-05-10 11:33) より:

たとえばいじわるですが、将来もしも2千円札が廃止されて代わりに8千円札が登場した場合、6万4千円を支払うとき、困りませんか?

これを、お題 Ver.2 とします。w



66,000円の場合、どうするのかな、と思ってしまった。

10,000円×4枚+8,000円×3枚+2,000円×1枚
10,000円×6枚+5,000円×1枚+1,000円×1枚

枚数が一緒になる場合の想定。

#ありきたりな金種表の課題なので、僕も何かの課題かな、と思いましたw

#以下、追記
66,000円じゃ、ダメじゃん。10,000円×5枚+8,000円×2枚が最低枚数だし・・・orz
わちゃさんのソースを実行してて気付いたよ・・・

[ メッセージ編集済み 編集者: ひろれい 編集日時 2007-05-10 14:06 ]
だっちょ
大ベテラン
会議室デビュー日: 2006/12/05
投稿数: 115
投稿日時: 2007-05-10 13:06
一応作成してみました.
java Test 64000 10000 8000 5000 2000 1000
で実行すると
金額:64000 = 10000 * 4 + 8000 * 3 + 5000 * 0 + 2000 * 0 + 1000 * 0
が最小でした。


コード:
import java.util.*;

/**
 * 第一引数が0以上の整数であれば、 
 * その表す金額を日本円で現金化したときに、 
 * その枚数が最小となる紙幣と硬貨の組み合わせを、 
 * 標準出力に出力するコンソールアプリケーションを作ってください。 
 */
public class Test {
    /**
     * メイン.
     */
    public static void main(String[] args) 
    {
        if (1 >= args.length) {
            System.out.println("java Test [金額] [紙幣1] [紙幣2] ...");
            System.out.println("金額を表現する紙幣で枚数が最小となるものを求める");
            return;
        }
        int sum = Integer.parseInt(args[0], 10);
        int[] units = new int[args.length-1];
        for (int i=0;i<units.length;i++) {
            units[i] = Integer.parseInt(args[i+1], 10);
        }
        int[] result = searchResult(sum, units);
        if (result==null) System.out.println("この金額は表現できません");
        else {
            System.out.print("金額:" + sum + " = ");
            for (int i=0;i<units.length;i++) {
                if (i>0) System.out.print(" + ");
                System.out.print(units[i]);
                System.out.print(" * " + result[i]);
            }
            System.out.println();
        }
    }

    /**
     * 最小枚数を求める
     * 単純にサーチすると硬貨種類のべき乗でサーチパスが
     * 爆発するので、有効なパスだけを記録して更新することにする
     * 
     * @param sum 総和となる金額
     * @param units 紙幣(硬貨)の単位
     */
    public static int[] searchResult(int sum, int[] units)
    {
        // 1つ前の長さのパスで有効なものの情報を記録する
        // Mapのkeyは硬貨枚数の配列で,valueはその総和
        // スピードはあまり気にしてない
        Map<int[], Integer> prev = new HashMap<int[], Integer>();
        int[] start = new int[units.length]; // すべて0の開始状態
        prev.put(start, 0);
        for (int n=1; ;n++) {  // nは枚数
            // 長さnのパスに対する金額を求める
            Map<int[], Integer> next = new HashMap<int[], Integer>();
            boolean any = false;
            for (Iterator<Map.Entry<int[], Integer>> iter=prev.entrySet().iterator(); iter.hasNext(); ) {
                Map.Entry<int[], Integer> entry = iter.next();
                int[] key = entry.getKey();
                int value = entry.getValue();
                for (int i=0;i<units.length;i++) {
                    int nextsum = value + units[i];
                    if (nextsum==sum) {
                        // 見つかった
                        key[i]++;
                        return(key);
                    }
                    else if (nextsum < sum) {
                        // 経路途中候補
                        int[] nextkey = new int[units.length];
                        for (int j=0;j<units.length;j++) nextkey[j] = key[j];
                        nextkey[i]++;
                        next.put(nextkey, nextsum);  // 重複しても1つ
                        any = true;
                    }
                }
            }
            // 候補がなくなれば表現できない金額だった
            if (!any) return(null);
            // 結果を記録
            prev = next;
        }
    }
}


わちゃ
大ベテラン
会議室デビュー日: 2005/12/05
投稿数: 162
お住まい・勤務地: 東京
投稿日時: 2007-05-10 13:31
私は、再帰を使ってやってみました。
こんなんで、どうでしょ?

コード:
    Private 金種リスト As Integer() = {10000, 8000, 1000, 500, 100, 50, 10, 5, 1}

    Public Function 枚数計算(ByVal 金額 As Integer, Optional ByVal 開始金種 As Integer = 0) As Integer()
        Dim 最適支払方法 As Integer() = New Integer(金種リスト.Length - 1) {}
        Dim 最適枚数 As Integer = Integer.MaxValue
        Dim 今回金種金額 As Integer = 金種リスト(開始金種)
        Dim 今回金種枚数 As Integer

        If 金額 Mod 今回金種金額 = 0 Then
            ' ぴったりだったら、それで OK
            最適支払方法(開始金種) = 金額 \ 今回金種金額
        Else
            ' ぴったりじゃなかったら、全部再帰で、計算
            For 今回金種枚数 = 金額 \ 今回金種金額 To 0 Step -1
                Dim 試行金額 As Integer = 金額 - 今回金種金額 * 今回金種枚数

                ' 次の金種で、自明に枚数が大きい場合は、ループ終了
                If (試行金額 \ 金種リスト(開始金種 + 1)) + 今回金種枚数 > 最適枚数 Then
                    Exit For
                End If
                ' とりあえず試す
                Dim 試行結果 As Integer() = 枚数計算(試行金額, 開始金種 + 1)

                ' 合計枚数を計算
                Dim 試行結果枚数 As Integer = 0
                Dim 枚数 As Integer
                For Each 枚数 In 試行結果
                    試行結果枚数 += 枚数
                Next

                ' 回数が少なかったら、代入
                If 最適枚数 > (今回金種枚数 + 試行結果枚数) Then
                    最適支払方法 = 試行結果
                    最適支払方法(開始金種) = 今回金種枚数
                    最適枚数 = 今回金種枚数 + 試行結果枚数
                End If
            Next
        End If
        Return 最適支払方法
    End Function

nagise
ぬし
会議室デビュー日: 2006/05/19
投稿数: 1141
投稿日時: 2007-05-10 13:42
最適化が難しいですね。

最大の単元のものから順に並べたときに、次の単元が前の単元の約数となっている
ケースでは大きいものから割り当てすればよさそうです。
数学的な証明はしていませんが。

例:{10000, 5000, 1000, 500, 100, 50, 10, 5, 1}

実は2000円札が含まれる場合、大きい順に割り当てて大丈夫か怪しい。
2000は5000の約数ではないので、
8000 = 5000x1 + 1000x3 = 2000x4
といったケースが出てきます。このケースでは数は逆転しないけども同点のものが出る。

単元が前の単元の約数になるグループごとに分けてから、
それぞれのグループへの割付を変えていくようなロジックだと
枝を大分刈り込めそうな気がします。
あとは、探査中でも現在の最小の枚数を超えたら打ち切るようにして大丈夫そう。

結構難解で、藪をつついたら蛇が出た気分。
だっちょ
大ベテラン
会議室デビュー日: 2006/12/05
投稿数: 115
投稿日時: 2007-05-10 13:53
一応プログラムを作成しましたが、先のコードでは91000円あたりになると
-Xmx100Mで起動してもOutOfMemoryErrorになります。
教育としては先のものでもいいと思いますが、実用という点ではまだかなり難点があり、分割サーチしないと使えないでしょう。
lalupin4
大ベテラン
会議室デビュー日: 2004/07/26
投稿数: 163
投稿日時: 2007-05-10 16:40
 もうちょっとOOで(C#):

コード:
class Program
{
    static void Main(string[] args)
    {
        Money m10000 = new Money(10000);
        Money m5000 = new Money(5000);
        Money m1000 = new Money(1000);
        Money m500 = new Money(500);
        Money m100 = new Money(100);
        Money m50 = new Money(50);
        Money m10 = new Money(10);
        Money m5 = new Money(5);
        Money m1 = new Money(1);

        m10000.Next += new NextEventHandler(m5000.Calculate);
        m5000.Next += new NextEventHandler(m1000.Calculate);
        m1000.Next += new NextEventHandler(m500.Calculate);
        m500.Next += new NextEventHandler(m100.Calculate);
        m100.Next += new NextEventHandler(m50.Calculate);
        m50.Next += new NextEventHandler(m10.Calculate);
        m10.Next += new NextEventHandler(m5.Calculate);
        m5.Next += new NextEventHandler(m1.Calculate);

        string temp = Console.ReadLine();
        int input = int.Parse(temp);
        Console.WriteLine(input.ToString("#,##0") + " : ");
        m10000.Calculate(input);
    }
}

class Money
{
    public event NextEventHandler Next;

    public Money(int amount)
    {
        this.Amount = amount;
    }

    public readonly int Amount;

    public void Calculate(int amount)
    {
        int restAmount = amount;
        int count = 0;

        while (restAmount >= this.Amount)
        {
            restAmount -= this.Amount;
            count++;
        }

        Console.WriteLine(this.Amount.ToString("#,##0").PadLeft(6, ' ') + " * " + count);

        if (this.Next != null)
        {
            this.Next(restAmount);
        }
    }
}

delegate void NextEventHandler(int restAmont);



参考:
http://blogs.wankuma.com/episteme/archive/2007/02/17/62740.aspx
lalupin4
大ベテラン
会議室デビュー日: 2004/07/26
投稿数: 163
投稿日時: 2007-05-10 16:42
 Ver. 2 は
上位の金種.金額 * 2 < 下位の金種.金額 * n <= 残金額 < 上下の最小公倍数
を満たすnをもつ金種を探せばイケるかな…。

検算:
10000 * 2 < 8000 * 3 < (64000-(10000*4)) < 50000

追記:これだと\66000のときはダメか…

[ メッセージ編集済み 編集者: lalupin4 編集日時 2007-05-10 17:01 ]

スキルアップ/キャリアアップ(JOB@IT)