- PR -

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

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

Anonymous Cowardさんの書き込み (2007-05-10 00:44) より:
<お題>
第一引数が0以上の整数であれば、
その表す金額を日本円で現金化したときに、
その枚数が最小となる紙幣と硬貨の組み合わせを、
標準出力に出力するコンソールアプリケーションを作ってください。

例)1625⇒1000*1+500*1+100*1+10*2+5*1

なお、表示形式は例と同じでなくて構いません。
わかりやすければどんな形式でも良いです。



Ver.1ですが、VB.NET版。
#お題からはずれる余計な機能が付いてますが、お気になさらず
#CIntのところは手抜きをしてます。

コード:
' コンパイル方法: vbc vbCashDispenser.vb /out:vbCashDispenser.exe
' 使用方法:vbCashDispenser.exe 12345 98765 999
Imports System
Imports System.Collections

Public Class CashDispenser
    ' 金種とその使用可能数(初期値)
    ' 使用しない金種には、使用可能数 -1を設定する
    Private m_units(,) As Integer = _
         {{10000, 400}, _
          {5000 ,  -1}, _
          {2000 , 100}, _
          {1000 , 800}, _
          {500  ,  -1}, _
          {100  , 200}, _
          {50   , 1}, _   ' 残量無しの実験用に減らしてます
          {10   , 100}, _
          {5    , 100}, _
          {1    , 100}}

    ' 金種リスト
    Private m_unitList As New ArrayList()

    Public Sub New()
        For i As Integer  = 0 To m_units.GetUpperBound(0)
            m_unitList.Add(New CashUnit(m_units(i, 0), m_units(i, 1)))
        Next
    End Sub

    ' 金種計算
    Public Sub DisplayCashUnitCount(ByVal amountOfMoney As Integer)
        Dim amountOfRemainingMoney As Integer = amountOfMoney

        Console.WriteLine("出金:{0}", amountOfMoney)

        For Each cashUnit As CashUnit In m_unitList
            If cashUnit.Count > 0 Then
                Dim requestCount As Integer = amountOfRemainingMoney \ cashUnit.Value
                Dim availableCount As Integer 
                If  requestCount > cashUnit.Count Then
                    availableCount = cashUnit.Count
                Else
                    availableCount = requestCount
                End If
                ' 単なる金種計算として使うなら以下の行をコメントアウトする
                cashUnit.Count -= availableCount

                Console.WriteLine("金種{0}:{1}枚", cashUnit.Value, requestCount)
                amountOfRemainingMoney -= cashUnit.Value * availableCount
            ElseIf cashUnit.Count = 0 Then
                Console.WriteLine("金種{0}:残量なし", cashUnit.Value)
            Else
                Console.WriteLine("金種{0}:非使用", cashUnit.Value)
            End If
        Next 
    End Sub

    Public Shared Sub Main(ByVal cmdArgs() As String)
         Dim cd As New CashDispenser()
         
         For Each arg As String In cmdArgs
             cd.DisplayCashUnitCount(CInt(arg))
         Next
    End Sub
End Class

' 金種クラス
Public Class CashUnit
    ' 使用可能数
    Private m_Count As Integer
    ' 金種の額面
    Private m_Value As Integer

    Public Property Count() As Integer
        Get
            Return m_Count
        End Get
        Set(ByVal value As Integer)
            m_Count = value
        End Set
    End Property

    Public ReadOnly Property Value() As Integer
        Get
            Return m_Value
        End Get
    End Property

    Public Sub New(ByVal value As Integer, ByVal count As Integer)
        m_Value = value
        m_Count = count
    End Sub
End Class

わちゃ
大ベテラン
会議室デビュー日: 2005/12/05
投稿数: 162
お住まい・勤務地: 東京
投稿日時: 2007-05-10 17:33
なにげに、面白いテーマですよね。

どのように割り当てるのが最適か悩ましいですね。

{100,50,49,48,47,1} で、141 円を支払う場合は、
47円を3枚が一番いいですよね。

そのため、nagise さんの言うような、次のものが約数になっている場合や、
lalupin さんのような上下の金種のみで、決めていくような
最適化も難しいようです。

結局は、かなりの通り数を総当たりでやらないといけない気がします。

自分のやつは、実際の日本円と同じ金種でやって、64001 円だと、
そこそこのスピードで、できますが、63999円だと、とたんに
計算回数が増えて、52万回も再帰呼び出しがありました。

なんとか、ならんかなぁ
sawat
大ベテラン
会議室デビュー日: 2006/08/02
投稿数: 112
投稿日時: 2007-05-10 17:42
これは部分和問題の一種と考えることができそうですね。
ただし、該当する部分集合のうちで、最も要素数の少ないものを選ばなければいけない。

ぴったり払えない場合は、お釣りの枚数を最小化するとか付け加えるともっと難しくなりますね。
びしばし
大ベテラン
会議室デビュー日: 2002/03/13
投稿数: 181
投稿日時: 2007-05-10 17:56
えーと、3年ほど前にこんな議論がされていたようですよ、と。

http://ml.tietew.jp/cppll/cppll_novice/article/157
nagise
ぬし
会議室デビュー日: 2006/05/19
投稿数: 1141
投稿日時: 2007-05-10 18:22
引用:

わちゃさんの書き込み (2007-05-10 17:33) より:
そのため、nagise さんの言うような、次のものが約数になっている場合や、
lalupin さんのような上下の金種のみで、決めていくような
最適化も難しいようです。



私の言いたかったことというのは、約数となる金種がある場合、
約数側のみを使って払うというのは枚数が増えることが明らかなので
走査の枝狩りができるのではないかということです。

要するに、10000円を5000円x2とか1000円x10とかに置き換えるのは
明らかに枚数が増えるだけだから、

金種A = {10000, 5000, 1000, 500, 100, 50, 10, 5, 1}
の中では大きいほうから優先して配すれば最適化されます。
これは大した時間をかけずに算出できますよね。

これと別に
金種B = {4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1}

という系がある混合系を考えるとすると、
たとえば31415円を払う場合、

A系で31413円、B系で2円払う
A系で31412円、B系で3円払う

A系で2円、B系で31413円払う

の31415パターンを試すことでO(n)の時間で計算できないかなぁ、
というアイデアなのでした。
もちろん、金種が素数だったりするとこれっぽっちも最適化されません orz

# どちらの計で何円払う、という判断の部分でも枝狩りは出来そうですけどね
lalupin4
大ベテラン
会議室デビュー日: 2004/07/26
投稿数: 163
投稿日時: 2007-05-10 18:49
 どうしましょ。次のお題募ります?
sawat
大ベテラン
会議室デビュー日: 2006/08/02
投稿数: 112
投稿日時: 2007-05-10 18:57
動的計画法で解いてみました。(言語はruby)

コード:

# !ruby

class Coin
attr_accessor :price, :count
def initialize(price, count=0)
@price = price
@count = count
end
def inspect
to_str
end
def to_str
"¥#{price}"
end
end

AMOUNT = (ARGV.shift || '46000').to_i
COIN_DATA = ARGV.empty? ? [10000, 5000, 1000, 500, 100, 50, 10, 5, 1] : ARGV.map{|i| i.to_i}
COIN_LIST = COIN_DATA.map{|i| Coin.new(i)}

def select_coin
p COIN_LIST
gain = [0] * (AMOUNT + 1)
choice = [nil] * (AMOUNT + 1)
COIN_LIST.each do |item|
size = item.price
price = item.price*100-1
(size..AMOUNT).each do |i|
space = i - size
new_value = price + gain[space]
if gain[i] < new_value
gain[i] = new_value
choice[i] = item
end
end
end

i = AMOUNT
while i > 0
item = choice[i]
item.count = item.count + 1
i = i - item.price
end

sum = 0
COIN_LIST.each do |item|
next if item.count == 0
print("%8d × %d\n" % [item.price, item.count])
sum += item.count
end
print "合計 : #{sum}枚\n"
end

select_coin



数万円程度ならなかなかのスピードで解けます。
コード:

> ruby coin.rb 63999 100 50 49 48 47 1
[¥100, ¥50, ¥49, ¥48, ¥47, ¥1]
100 × 639
50 × 1
49 × 1
合計 : 641枚



参考:http://www.geocities.jp/m_hiroi/light/pyalgo23.html
というかほとんど丸写し。

#編集
結果表示を変更とコマンドライン引数でコイン種別を取れるように変更。
さらに、実行例を追加。

[ メッセージ編集済み 編集者: sawat 編集日時 2007-05-11 00:39 ]
だっちょ
大ベテラン
会議室デビュー日: 2006/12/05
投稿数: 115
投稿日時: 2007-05-10 19:54
 似たようなのに,最長共通部分列がありますよね。
 別に最長でなくてもいいのですが、変動したテストログの最長共通部分から正規表現(もどき)を作成して、その後のテストログで異常が出力されてないかパターンチェックするようなことをやってみたんですが、1つのテストログファイルが数kbyteレベルを超えると、やはりメモリ不足になってパターン表現が作成できなかったので、いいアルゴリズムがあればちょっと大きなログが出力されるテストでもそのまま使えるなと思ってました。
 私が作ったのは
http://hp.vector.co.jp/authors/VA007799/viviProg/doc5.htm
のアルゴリズムから1文字のみの一致は評価点を下げ、特異点での値だけを覚えるようにしてテーブル(リスト)を小さくしたもので、通常のデータだと2%くらいのサイズになりました。(オブジェクトをリストにしているので本当のメモリ節約はしてませんが)

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