challenge 税込み価格への修正

ここにチラシの原稿があります。例えば「ダイコン150円、ハクサイ120円、ジャガイモ30円」のような文字列です。法改正によって商品の値段は税込み表示にしないといけなくなりました。そこで、与えられた文字列の中から税抜き価格を見つけ出し、税込み価格に変更した文字列を返す関数を作ってください。

なお、税抜き価格は半角の数字の連なりで、かつ半角の数字の連なりはすべて税抜き価格だとします。「9,800円」「百円」「100円」「100g80円」などのような記述はないと考えてかまいません。 また税込み価格は税抜き価格の1.05倍で、端数は切り捨てとしてください。

Posted feedbacks - Nested

Flatten Hidden
関数にはしていませんが…(^_^;)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
| inString in out |
inString := 'ダイコン150円、ハクサイ120円、ジャガイモ30円'.
in := inString readStream.
out := String new writeStream.
[in atEnd] whileFalse: [
   in peek isDigit
      ifTrue: [out print: ((Integer readFrom: in) * 1.05) floor]
      ifFalse: [out nextPut: in next]].
^out contents

"=> 'ダイコン157円、ハクサイ126円、ジャガイモ31円' "
まぁシンプルに。
 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
import java.math.BigDecimal;

public class ZeikomiHyoki {
	
	public static void main(String[] args) {
		for (int i = 0; i < args.length; i++) {
			System.out.println(new ZeikomiHyoki().execute(args[i]));
		}
	}
	
	public String execute(String string) {
		StringBuffer buffer = new StringBuffer();
		StringBuffer integralBuffer = new StringBuffer();
		boolean flg = false;
		for (int i = 0; i < string.length(); i++) {
			if (48 <= (int) string.charAt(i) && (int) string.charAt(i) <= 57) {
				if (!flg) {
					flg = true;
					integralBuffer = new StringBuffer();
				}
				integralBuffer.append(string.charAt(i));
			} else {
				if (flg) {
					flg = false;
					buffer.append(calc(integralBuffer.toString()));
				}
				buffer.append(string.charAt(i));
			}
		}
		return buffer.toString();
	}

	private String calc(String string) {
		System.out.println(string);
		return new BigDecimal(string).multiply(new BigDecimal("1.05")).setScale(0, BigDecimal.ROUND_DOWN).toString();
	}

}
身も蓋もないけど。
1
2
3
function Convert2TaxedHandout(s) {
  return s.replace(/[1-9][0-9]*/g, function(p) { return Math.floor(p * 1.05); });
}
1
perl -pe 's/(\d+)/int $1*1.05/eg'
ruby でも gsub ですかね。
1
% ruby -pe '$_.gsub!(/\d+/){|s| (s.to_i * 1.05).to_i}'
どなたかかエレガントなコードをお願いします><
1
2
3
import re
def include_tax(s):
    return re.sub('\d+', lambda m: str(int(int(m.group(0)) * 1.05)), s)
文字列に多バイト文字が来ることもあるんですよね?
Rubyの場合はKCODEを指定してやるだけですが。
1
2
3
4
5
<?php
function conv_with_tax($str) {
  return mb_ereg_replace("(\d+)", "intval(1.05 * \\1)", $str, "e");
}
?>
 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
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class ExciseTax {
	
	private double rate;
	
	public ExciseTax(double rate) {
		this.rate = 1.0 + rate;
	}
	
	public String includeTax(String string) {
		Pattern p = Pattern.compile("\\d+");
		Matcher m = p.matcher(string);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, calcTaxIncludedPrice(m.group()));
		}
		m.appendTail(sb);
		return sb.toString();
	}
	
	private String calcTaxIncludedPrice(String price) {
		return Long.toString((long)(Long.parseLong(price) * rate));
	}
	
	public static void main(String[] args) {
		System.out.println(new ExciseTax(0.05).includeTax("ダイコン150円、ハクサイ120円、ジャガイモ30円"));
	}
	
}
初めてcl-ppcreを使ってみました
1
2
3
4
5
6
7
8
9
(require 'cl-ppcre)
(defun including-tax (string)
  (cl-ppcre:regex-replace-all
   "\\d+" string
   (lambda (num) (format nil "~a" (floor (* (parse-integer num) 1.05))))
   :simple-calls t))

;; CL-USER> (including-tax "ダイコン150円、ハクサイ120円、ジャガイモ30円")
;; "ダイコン157円、ハクサイ125円、ジャガイモ31円"
何で白菜が125円なんだろう。
浮動小数点の誤差?
たしかに、120 * 1.05はちょうど126.0になるはずですね…
誤差っぽいですね。Common Lispは分数が使えるので1.05→21/20に変えてみたら126になりました。
ノーマルモードで実行
1
:%s!\(\d\+\)!\=submatch(0)*105/100!g
C# 2.0 (ECMA C# 3rd edition) 以降。呼び出すたびにRegExインスタンスが作成されるのは無駄なんですが。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static string PlusTax(string expression)
{
    return new System.Text.RegularExpressions.Regex("[0-9]+").Replace(expression, delegate(Match m)
    {
        int price;
        int.TryParse(m.Value, out price);
        price = (price * 105) / 100;
        return price.ToString();
    });
}
1
2
3
def add_tax(str)
  str.gsub(/\d+/){($&.to_i * 1.05).to_i}
end
上の方の人と丸被りだった。orz 申し訳ない。
正規表現はやっぱり苦手だ。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# -*- coding: utf-8 -*-

def toTaxStr(s):
    out = ""
    items = s.split("円")
    for item in items[0:-1]:
        kakaku = ""
        while(len(item) and item[-1].isdigit()):
            kakaku = item[-1] + kakaku
            item = item[0:-1]
        out += item
        if len(kakaku):
            out += str(int(int(kakaku)*1.05))
        out += "円"
    return out + items[-1]


sample_str = "ダイコン150円、ハクサイ120円、ジャガイモ30円"
print unicode(toTaxStr(sample_str),"utf-8")
(\d*円)を修正する問題だと勘違いしていたorz。
何の工夫もなしに
 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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

char* tax(char* src,char* dest){
	char *p;
	char buf[256];
	long price;
	
	p=src;
	dest[0]='\0';
	while(*p){
		if(isdigit(*p)){
			price=strtol(p,&p,10);
			price=price*105/100;
			sprintf(buf,"%ld",price);
			strcat(dest,buf);
		}else{
			strncat(dest,p,1);
			p++;
		}
	}
	return dest;
}

int main(){
	char buf[256];
	
	tax("ダイコン150円、ハクサイ120円、ジャガイモ30円",buf);
	printf("%s\n",buf);
	tax("PC150000円、車2000000円",buf);
	printf("%s\n",buf);
	return 0;
}
税課税により、桁数が上がっても問題ないようにしたはず。
  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
104
105
106
107
108
109
110
111
112
113
114
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct node Node;
struct node {
    char *string;
    Node *next;
};

char *zei(char *);
void split(Node *head, char *string);

int main()
{
    char *result_string;
    
    result_string = zei("ダイコン150円、ハクサイ120円、ジャガイモ30円");
    printf("%s\n", result_string);
    free(result_string);

    result_string = zei("ほげ8888円、ぴよ9999円");
    printf("%s\n", result_string);
    free(result_string);

    return 0;
}

char *zei(char *original)
{
    Node list;
    Node *iter;
    Node *iter2;
    int  value;
    int  count;
    char *result;

    split(&list, original);

    /* 税追加と文字数のカウント */
    count = 0;
    iter  = &list;
    while (iter->next != NULL) {
        if (iter->string[0] >= '0' && iter->string[0] <= '9') {
            sscanf(iter->string, "%d", &value);
            sprintf(iter->string, "%d", (int)(value * 1.05));
        }
        count += strlen(iter->string);
        iter = iter->next;
    }

    /* 連結 */
    result    = malloc(sizeof(char) * (count+1));
    result[0] = '\0';
    iter      = &list;
    while (iter->next != NULL) {
        strcat(result, iter->string);
        free(iter->string);
        iter = iter->next;
    }

    iter = list.next;
    while (iter->next != NULL) {
        iter2 = iter;
        iter  = iter->next;
        free(iter2);
    }
    free(iter);

    return result;
}

void split(Node *head, char *string)
{
    Node *now;
    int count;

    head->string = NULL;
    head->next   = NULL;
    now   = head;
    count = 0;
    while (*string != '\0') {
        while (string[count] >= '0' && string[count] <= '9') {
            count++;
            if (string[count] == '\0')
                break;
        }
        now->string = malloc(sizeof(char) * (count+1+1)); /* 桁上がり分 */
        strncpy(now->string, string, count);
        now->string[count] = '\0';
        now->next = malloc(sizeof(Node));
        now       = now->next;
        now->string = NULL;
        now->next = NULL;
        string    = string + count;
        count     = 0;
        if (*string == '\0')
            break;
        while (!(string[count] >= '0' && string[count] <= '9')) {
            count++;
            if (string[count] == '\0')
                break;
        }
        now->string = malloc(sizeof(char) * (count+1));
        strncpy(now->string, string, count);
        now->string[count] = '\0';
        now->next = malloc(sizeof(Node));
        now       = now->next;
        now->string = NULL;
        now->next = NULL;
        string    = string + count;
        count     = 0;
    }
}
(including-tax "ダイコン150円、ハクサイ120円、ジャガイモ30円")

=>"ダイコン157円、ハクサイ126円、ジャガイモ31円"
1
2
3
(define (including-tax str)
  (let ((rep (lambda (m) (x->integer (floor (* (string->number (m 0)) 1.05))))))
    (regexp-replace-all #/\d+/ str rep)))
(included-tax "ダイコン150円、ハクサイ120円、ジャガイモ30円")

=>"ダイコン157円、ハクサイ126円、ジャガイモ31円"
1
2
3
(defun including-tax (str)
  (let ((rep (lambda (m) (number-to-string (floor (* (string-to-number m) 1.05))))))
    (replace-regexp-in-string "[0-9]+" rep str)))
一瞬、投稿ミスで重複になってしまったのかと思ってしまいました(笑)
回答済みだけど、メモ代わりに。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using System;
using System.Text.RegularExpressions;
class Program
{
  static void Main()
  {
    string s = "test100xyz400";
    Console.WriteLine(ReplaceTax(s));
  }
  public static string ReplaceTax(string s)
  {
    return Regex.Replace(s, @"\d+", delegate(Match m)
    {
      return ((int)(int.Parse(m.Value) * 1.05)).ToString();
    });
  }
}
ちゃんとfloor使ってやってみる
1
perl -MPOSIX -pe 's/(\d+)/floor($1*1.05)/eg'
C++のSTLを使ってみました。bcc32(5.5)で確認。
 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
#include <iostream>
#include <sstream>
#include <cctype>
#include <string>
#include <algorithm>
#include <functional>

std::string convert_do(const std::string& s)
{
    std::istringstream sin(s);

    int price;

    sin >> price;

    price *= 1.05;

    std::ostringstream sout;

    sout << price;

    return sout.str();
}

struct isdigit : std::unary_function<char, bool>
{
    bool operator()(char c) const
    {
        return std::isdigit(c);
    }
};

std::string convert(const std::string& s)
{
    std::string ret;

    std::string::const_iterator beg = s.begin();
    std::string::const_iterator end = s.end();

    while (true)
    {
        std::string::const_iterator cur;

        cur = std::find_if(beg, end, isdigit());

        ret.append(std::string(beg, cur)); beg = cur;

        if (beg == end)
        {
            break;
        }

        cur = std::find_if(beg, end, std::not1(isdigit()));

        ret.append(convert_do(std::string(beg, cur))); beg = cur;
    }

    return ret;
}

int main()
{
    std::cout << convert("ƒ_ƒCƒRƒ“150‰~AƒnƒNƒTƒC120‰~AƒWƒƒƒKƒCƒ‚30‰~") << std::endl;
}
浮動小数点数だと、100 * 1.05 => 105 のようにちょうど整数になったとき、計算誤差によっては104になってしまうかもしれないので、整数のままで処理するように修正。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
--- main.cpp.orig	Wed Jul 11 15:03:27 2007
+++ main.cpp	Wed Jul 11 15:01:35 2007
@@ -13,11 +13,9 @@
 
     sin >> price;
 
-    price *= 1.05;
-
     std::ostringstream sout;
 
-    sout << price;
+    sout << (price * 105 / 100);
 
     return sout.str();
 }
1
2
3
4
5
6
function tax(text)
  return string.gsub(text, "%d+",
    function(amt)
      return tostring(math.floor(amt * 1.05))
    end)
end
UTF-8の入力で確認。 他では文字化けするかもしれません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import Char
includeTax s = inText s
    where inText []                    = []
          inText (x:xs) | isDigit x    = inPrice xs [x]
                        | otherwise    = x : inText xs
          inPrice (x:xs) y | isDigit x = inPrice xs (y ++ [x])
          inPrice xs y                 = tax y ++ inText xs
          tax s                        = show $ floor $ 1.05 * read s

main = getContents >>= (putStr . includeTax)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
module doukaku;
private import std.stdio;
private import std.string;
private import std.regexp;
char[] including_tax(char[] str) {
    return sub(str, r"[0-9]+", delegate(RegExp re) {
            long price = atoi(re.match(0));
            return format("%d", price*21/20);
        }, "g");
}
void main() {
    writefln(including_tax("ダイコン150円、ハクサイ120円、ジャガイモ30円"));
}
実際には日本語でコケますが・・・
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import scala.io._
import java.util.regex._

class ExtendedString(self:String) {
  def gsub(reg:String)(f:(String)=>String) = {
    val result = new StringBuffer
    val m = Pattern.compile("\\d+").matcher(self)
    while(m.find) m.appendReplacement(result, f(m.group))
    m.appendTail(result)
    result.toString
  }
  
  def taxed():String = {
    gsub("\\d+"){x => (Integer.parseInt(x)*1.05).asInstanceOf[Int].toString}
  }
}
implicit def string2ext(self:String) = new ExtendedString(self)

"ダイコン150円、ハクサイ120円、ジャガイモ30円".taxed
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
「ダイコン150円、ハクサイ120円、ジャガイモ30円」を税込み価格で表示

●税込み価格(原稿を)
 箱とは配列
 
 原稿を「円」で区切って反復
  対象を「\d+$」で正規表現マッチ
  価格はそれの1.05倍の整数部分
  対象の「\d+$」を「{価格}円」へ正規表現置換
  箱にそれを配列追加
 
 箱を空で配列結合して戻す
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
open System;;
open System.Text.RegularExpressions;;

let yen:string = "円";;

let tax str =
    let re = new Regex( "\d+" ^ yen ) in
    let matches = Seq.to_list { for m in re.Matches( str ) -> m.ToString() } in
    let getPrice (s:string) = Int32.Parse(s.Replace(yen, "")) in
    let taxes s = ((getPrice s) * 105 / 100).ToString() ^ yen in
    let rec replace (base:string) = function
        | [] -> base
        | m::ms -> replace (base.Replace(m,(taxes m))) ms in
    replace str matches;;
string.Templateを使ったバージョン。かなり冗長。orz
ユニコード文字列と普通の文字列の両方に対応。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# -*- coding: utf-8 -*-
import re, string

def include_tax(