返回列表 发帖

批处理脚本实现C语言趣味编程百例011:打鱼还是晒网

加分:1个技术分或者10个PB

要求用BAT脚本实现:

中国有句俗语叫“三天打鱼两天晒网”。某人从1990年1月1日起开始“三天打鱼两天晒网”,问这个人在以后的某一天中是“打鱼”还是“晒网”。

*问题分析与算法设计
根据题意可以将解题过程分为三步:
1)计算从1990年1月1日开始至指定日期共有多少天;
2)由于“打鱼”和“晒网”的周期为5天,所以将计算出的天数用5去除;
3)根据余数判断他是在“打鱼”还是在“晒网”;
若 余数为1,2,3,则他是在“打鱼”
否则 是在“晒网”
在这三步中,关键是第一步。求从1990年1月1日至指定日期有多少天,要判断经历年份中是否有闰年,二月为29天,平年为28天。闰年的方法可以用伪语句描述如下:
如果 ((年能被4除尽 且 不能被100除尽)或 能被400除尽)
则 该年是闰年;
否则 不是闰年。
C语言中判断能否整除可以使用求余运算(即求模)

*程序说明与注释
#include<stdio.h>
int days(struct date day);
struct date{
int year;
int month;
int day;
};
int main()
{
struct date today,term;
int yearday,year,day;
printf("Enter year/month/day:");
scanf("%d%d%d",&today.year,&today.month,&today.day); /*输入日期*/
term.month=12; /*设置变量的初始值:月*/
term.day=31; /*设置变量的初始值:日*/
for(yearday=0,year=1990;year<today.year;year++)
{
term.year=year;
yearday+=days(term); /*计算从1990年至指定年的前一年共有多少天*/
}
yearday+=days(today); /*加上指定年中到指定日期的天数*/
day=yearday%5; /*求余数*/
if(day>0&&day<4) printf("he was fishing at that day.\n"); /*打印结果*/
else printf("He was sleeping at that day.\n");
}
int days(struct date day)
{
static int day_tab[2][13]=
{{0,31,28,31,30,31,30,31,31,30,31,30,31,}, /*平均每月的天数*/
{0,31,29,31,30,31,30,31,31,30,31,30,31,},
};
int i,lp;
lp=day.year%4==0&&day.year%100!=0||day.year%400==0;
/*判定year为闰年还是平年,lp=0为平年,非0为闰年*/
for(i=1;i<day.month;i++) /*计算本年中自11日起的天数*/
day.day+=day_tab[lp][i];
return day.day;
}COPY
*运行结果
Enter year/month/day:1991 10 25
He was fishing at day.
Enter year/month/day:1992 10 25
He was sleeping at day.
Enter year/month/day:1993 10 25
He was sleeping at day.

*思考题
请打印出任意年份的日历
2

评分人数

本帖最后由 terse 于 2012-4-3 02:20 编辑

回复 3# HAT
@echo off&setlocal enabledelayedexpansion
set/p Dates=输入年月日(空格隔开)
for /f "tokens=1-3" %%i in ("%Dates%") do set /a Y=%%i,M=100%%j%%100,D=100%%k%%100
set _0=打鱼&set _1=晒网
set /a "JD=(d-2479968+1461*(y+4800+(m-14)/12)/4+367*(m-2-(m-14)/12*12)/12-3*(y+4900+(m-14)/12)/100/4)%%5/3"
echo !_%JD%!
pauseCOPY
1

评分人数

TOP

回复 2# terse


我要代码

TOP

给你代码,我也要分
@echo off & setlocal enabledelayedexpansion
set /a "p=1990-1+400,di1990_1_1=1990*365+p/4-p/100+p/400"
for %%a in (y m d) do set /p "%%a=%%a: "
set /a "p=y-1+400,m-=1,r=-di1990_1_1+d-1+y*365+p/4-p/100+p/400+m*31+(~(m-4)>>31)+(~(m-6)>>31)+(~(m-9)>>31)+(~(m-11)>>31)+((~(m-2)>>31)&-2-(^!^!(y&3)|(^!^!(y&0xf)&^!0x!y:~-2!))),r%%=5"
if %r% lss 3 (echo 打鱼) else echo 晒网COPY
理解为: 1990.1.1  ~ 1990.1.3 打鱼, 1990.1.4 ~ 1990.1.5 晒网, ...
故余数就是 0 ~ 2 打鱼, 3,4 晒网

日历有: 简明万年历
1

评分人数

    • plp626: 都是O(1)型的;技术 + 1

TOP

本帖最后由 plp626 于 2012-4-3 19:04 编辑

回复 2# terse


没测试;但看上去确实是完美版的 短小精悍;
看了老半天没明白算法思想;
求解释;
计算1900-1-1到指定年份日期之间的天数是如何算的?什么公式?
xm=(m-14)/12               ;1-2月的月份映射为-1;3-12月的月份映射为0
JD=d-2479968               ;常数来历?
+
1461*(y+4800+xm)/4   ;1461来历?
+
367*(m-2-xm*12)/12    ; 367来历?
-
3*(y+4900+xm)/100/4  ;4900 来历?;貌似这里可以精简COPY

TOP

本帖最后由 CrLf 于 2012-4-3 21:13 编辑

调用函数~
@echo off
call :date2days 1990 1 1 2012 04 03 var
set /a var%%=5
if %var% leq 3 (echo 打鱼) else echo 晒网
pause&exit
:::::::::::::::::::::::::::::日期转天数函数::::::::::::::::::::::::::::
:date2days
:: 用法1:计算从 1970-01-01 到指定日期之间所经过的天数对应的日历日期。
::  call :Date2Days %yy% %mm% %dd% days
::  参数:%1 待转换的年
::        %2 待转换的月,可以以零开头
::        %3 待转换的日,可以以零开头
::        %4 该变量用于接收所经过的天数(可为空)
::
:: 用法2:计算两个日期的天数差值
::  call :Date2Days %y1% %m1% %d1% %y2% %m2% %d2% days
::  参数:%1 待转换的年1
::        %2 待转换的月1,可以以零开头
::        %3 待转换的日1,可以以零开头
::        %4 待转换的年2
::        %5 待转换的月2,可以以零开头
::        %6 待转换的日2,可以以零开头
::        %7 该变量用于接收所经过的天数(可为空)
::
:: 注:可选项若为空,则显示结果到屏幕,否则把结果存储到相应变量中
::
::                                Made by Crlf     http://bbs.bathome.net
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
setlocal disabledelayedexpansion
set/a days=10%~3%%100-719050+30*(m=10%~2%%100)+m/9*-~m/2+!(m/9)*m/2+!!(m/3)*(!((y=%1)%%4)-!(y%%100)+!(y%%400)-2)+y*365+~-y/4-~-y/100+~-y/400-396-%50/10*!%60
(endlocal&set %7=%days%||set %4=%days%||echo %days%
if %6. neq . call %0 %4 %5 %6 %7 %days%) 2>nul
if /i %0==:date2days exit/b0COPY
函数未独立发帖,仅包含于此贴压缩包:http://www.bathome.net/thread-15186-1-4.html
1

评分人数

TOP

本帖最后由 CrLf 于 2012-4-3 21:30 编辑

该函数在区分大小月时采用了独创的算法,所以比较简洁
就题解题就改成这样:
@echo off
set /a y=2012,m=4,d=3
set /a var=d-726467+30*m+m/9*-~m/2+!(m/9)*m/2+!!(m/3)*(!(y%%4)-!(y%%100)+!(y%%400)-2)+y*365+~-y/4-~-y/100+~-y/400-396,var%%=5
if %var% leq 3 (echo 打鱼) else echo 晒网
pauseCOPY
其中用了大量的 ~-N,其实它与 N-1 等效(同理,-~N 与 N+1 等效),但由于 ~ 与 -(负号) 的优先级高于乘除运算,故 ~-N 可节省一对括号
当然还可以再简洁点,不过估计更没人看得懂了...
@echo off
set /a y=2012,m=4,d=3
set /a "1/((d-726467-396+30*m+m/9*-~m/2+!(m/9)*m/2+!!(m/3)*(!(y%%4)-!(y%%100)+!(y%%400)-2)+y*365+~-y/4-~-y/100+~-y/400)%%5/4)" 2>nul&&echo echo 晒网||echo 打鱼
pauseCOPY
回复 9# HAT
算法简单解释:
set /a var=d-726863+30*m+m/9*-~m/2+!(m/9)*m/2+!!(m/3)*(!(y%%4)-!(y%%100)+!(y%%400)-2)+y*365+~-y/4-~-y/100+~-y/400,var%%=5
d-726467
rem 用日减去 1970-01-01~1990-01-01 之间的天数
30*m+m/9*-~m/2+!(m/9)*m/2
rem 先将月份统一乘 30 天,若m大于9,则将日期减去月份加1的一半,否则减去月份的一半
!!(m/3)*(!(y%%4)-!(y%%100)+!(y%%400)-2)
rem 当月份大于2的时候,天数减2,还要判断年份是否为闰年,如果是则加1
y*365+~-y/4-~-y/100+~-y/400-396
rem 先将年份统一乘365天,再减去闰年的数量
-396
rem 消除年月均从0开始计算产生的多余天数
var%%=5
rem 将所得的日期偏移量求余COPY
1

评分人数

TOP

回复 4# fatcat


各位大神详细讲解一下自己的日期算法吧

TOP

回复 6# CrLf


各位大神详细讲解一下自己的日期算法吧

TOP

回复 5# plp626
用的计算儒略日数公式  当然还有许多其它公式

TOP

本帖最后由 plp626 于 2012-4-13 21:06 编辑

为这个代码,费神不少,彻底搞懂了;
@echo off&setlocal enabledelayedexpansion
set 这天=2012-4-13
set _0=打渔&set _1=晒网
for /f "tokens=1-3 delims=-" %%a in ("%这天%")do set/a y=%%a,m=%%b,d=%%c
set/a "m=(m+9)%%12,y-=m/10+1989,i=(y/4+(m*153+2)/5+d)%%5/3"
set _%i%!
pauseCOPY
算法见:
http://www.bathome.net/thread-16147-1-1.html

TOP

返回列表