How to verify ISBN and calculate checksum digit in COBOL? - validation

My problem is I get a different out put from what I am supposed to get:
look very below for the output wanted. here is the output I get:
978-1734314502 (correct and valid)
978-1734314509 (incorrect, contains a non-digit)
978-1788399081 (correct and valid)
978-1788399083 (incorrect, contains a non-digit)
Here is what's the question is asking me to do:
do a modern Cobol program to perform ISBN validation of a series of 10-digit ISBNs
stored in a user-inputted file.
Include three “subprograms” in the form of paragraphs:
2.1. readISBNnum - Prompts the user for the name of an ASCII file containing the list of ISBN
numbers. Reads the values of the ISBN numbers and processes them. If the file does not
exist, the program should produce an error message and re-prompt for the filename.
2.2. isValidate - Checks the validity of the ISBN, i.e. whether or not it contains
characters it shouldn’t. Responses should include an indication of whether a number
contains erroneous characters.
2.3. checkSUM - Extracts the individual digits, and calculates the checksum digit.
Produce an output for each ISBN number in the file, identifying whether it is valid or not
Here is what I have done so far:
IDENTIFICATION DIVISION.
PROGRAM-ID. testSubs.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
FUNCTION ALL INTRINSIC
FUNCTION validISBN13.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
DATA DIVISION.
FILE SECTION.
WORKING-STORAGE SECTION.
01 IX PIC S9(4) COMP.
01 TEST-ISBNS.
02 FILLER PIC X(14) VALUE '978-1734314502'.
02 FILLER PIC X(14) VALUE '978-1734314509'.
02 FILLER PIC X(14) VALUE '978-1788399081'.
02 FILLER PIC X(14) VALUE '978-1788399083'.
01 TEST-ISBN REDEFINES TEST-ISBNS
OCCURS 4 TIMES
PIC X(14).
PROCEDURE DIVISION.
MAIN-PROCEDURE.
PERFORM
VARYING IX
FROM 1
BY 1
UNTIL IX > 4
DISPLAY TEST-ISBN (IX) ' ' WITH NO ADVANCING
END-DISPLAY
IF validISBN13(TEST-ISBN (IX)) = -1
DISPLAY '(incorrect, contains a non-digit)'
ELSE
DISPLAY '(correct and valid)'
END-IF
END-PERFORM.
GOBACK.
END PROGRAM testSubs.
IDENTIFICATION DIVISION.
FUNCTION-ID. validISBN13.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
FUNCTION ALL INTRINSIC.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
DATA DIVISION.
FILE SECTION.
WORKING-STORAGE SECTION.
01 PASSED-SIZE PIC S9(6) COMP-5.
01 IX PIC S9(4) COMP.
01 WORK-FIELDS.
02 WF-DIGIT PIC X.
02 WF-COUNT PIC 9(2).
88 WEIGHT-1 VALUE 1, 3, 5, 7, 9, 11, 13.
88 WEIGHT-3 VALUE 2, 4, 6, 8, 10, 12.
02 WF-SUM PIC S9(8) COMP.
LINKAGE SECTION.
01 PASSED-ISBN PIC X ANY LENGTH.
01 RETURN-VALUE PIC S9.
PROCEDURE DIVISION USING PASSED-ISBN
RETURNING RETURN-VALUE.
CALL 'C$PARAMSIZE'
USING 1
GIVING PASSED-SIZE
END-CALL.
COMPUTE-CKDIGIT.
INITIALIZE WORK-FIELDS.
PERFORM
VARYING IX
FROM 1
BY 1
UNTIL IX GREATER THAN PASSED-SIZE
MOVE PASSED-ISBN (IX:1) TO WF-DIGIT
IF WF-DIGIT IS NUMERIC
ADD 1 TO WF-COUNT
IF WEIGHT-1
ADD NUMVAL(WF-DIGIT) TO WF-SUM
ELSE
COMPUTE WF-SUM = WF-SUM +
(NUMVAL(WF-DIGIT) * 3)
END-COMPUTE
END-IF
END-IF
END-PERFORM.
IF MOD(WF-SUM, 10) = 0
MOVE +0 TO RETURN-VALUE
ELSE
MOVE -1 TO RETURN-VALUE
END-IF.
GOBACK.
===================================================================================
IDENTIFICATION DIVISION.
PROGRAM-ID. sedol.
ENVIRONMENT DIVISION.
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT sedol-file ASSIGN "sedol.txt"
ORGANIZATION LINE SEQUENTIAL
FILE STATUS sedol-file-status.
DATA DIVISION.
FILE SECTION.
FD sedol-file.
01 sedol PIC X(6).
WORKING-STORAGE SECTION.
01 sedol-file-status PIC XX.
88 sedol-file-ok VALUE "00".
01 digit-num PIC 9 COMP.
01 digit-weights-area VALUE "1317391".
03 digit-weights PIC 9 OCCURS 7 TIMES.
01 weighted-sum-parts-area.
03 weighted-sum-parts PIC 9(3) COMP OCCURS 6 TIMES.
01 weighted-sum PIC 9(3) COMP.
01 check-digit PIC 9.
PROCEDURE DIVISION.
OPEN INPUT sedol-file
PERFORM UNTIL NOT sedol-file-ok
READ sedol-file
AT END
EXIT PERFORM
END-READ
MOVE FUNCTION UPPER-CASE(sedol) TO sedol
PERFORM VARYING digit-num FROM 1 BY 1 UNTIL digit-num > 6
EVALUATE TRUE
WHEN sedol (digit-num:1) IS ALPHABETIC-UPPER
IF sedol (digit-num:1) = "A" OR "E" OR "I" OR "O" OR "U"
DISPLAY "Invalid SEDOL: " sedol
EXIT PERFORM CYCLE
END-IF
COMPUTE weighted-sum-parts (digit-num) =
(FUNCTION ORD(sedol (digit-num:1)) - FUNCTION ORD("A")
+ 10) * digit-weights (digit-num)
WHEN sedol (digit-num:1) IS NUMERIC
MULTIPLY FUNCTION NUMVAL(sedol (digit-num:1))
BY digit-weights (digit-num)
GIVING weighted-sum-parts (digit-num)
WHEN OTHER
DISPLAY "Invalid SEDOL: " sedol
EXIT PERFORM CYCLE
END-EVALUATE
END-PERFORM
INITIALIZE weighted-sum
PERFORM VARYING digit-num FROM 1 BY 1 UNTIL digit-num > 6
ADD weighted-sum-parts (digit-num) TO weighted-sum
END-PERFORM
COMPUTE check-digit =
FUNCTION MOD(10 - FUNCTION MOD(weighted-sum, 10), 10)
DISPLAY sedol check-digit
END-PERFORM
CLOSE sedol-file
.
END PROGRAM sedol.
However, I should get the output to look like this:
1856266532 correct and valid
0864500572 correct and valid with leading zero
0201314525 correct and valid with leading zero
159486781X correct and valid with trailing uppercase X
159486781x correct and valid with trailing lowercase X
0743287290 correct and valid with leading and training zero
081185213X correct and valid with leading zero, trailing X
1B56266532 incorrect, contains a non-digit
159486781Z incorrect, contains a non-digit/X in check digit
1856266537 correct, but not valid (invalid check digit)

Based on the assignment given in the post, the following code implements 2.2 isValidate and 2.3 checkSUM procedures for 10-digit ISBNs and 3. identifies each sample ISBN as valid or not.
Code:
data division.
working-storage section.
01 isbn-table.
03 isbn-test-numbers.
05 pic x(10) value "1856266532".
05 pic x(10) value "0864500572".
05 pic x(10) value "0201314525".
05 pic x(10) value "159486781X".
05 pic x(10) value "159486781x".
05 pic x(10) value "0743287290".
05 pic x(10) value "081185213X".
05 pic x(10) value "1B56266532".
05 pic x(10) value "159486781Z".
05 pic x(10) value "1856266537".
03 isbn-10-number redefines isbn-test-numbers
pic x(10) occurs 10 indexed isbn-idx.
01 isbn-work.
03 isbn-digit pic 9 occurs 9.
03 pic x.
01 check-digit.
03 check-digit-9 pic 9.
01 digit-position comp pic 9(4).
01 digit-weight comp pic 9(4).
01 weighted-sum comp pic 9(4).
01 validation-flags.
88 no-messages value all "0".
03 pic 9.
88 invalid-checksum value 1.
03 pic 9.
88 invalid-content value 1.
01 isbn-message pic x(50).
procedure division.
perform varying isbn-idx from 1 by 1
until isbn-idx > 10
move isbn-10-number (isbn-idx) to isbn-work
perform isValidate
display isbn-message
end-perform
stop run
.
isValidate.
set no-messages to true
if isbn-work (1:9) is numeric
perform checkSUM
if function upper-case (isbn-work (10:1))
not equal check-digit
set invalid-checksum to true
end-if
else
set invalid-content to true
end-if
move spaces to isbn-message
evaluate true
when invalid-checksum
string isbn-work " invalid checksum"
delimited size into isbn-message
when invalid-content
string isbn-work " invalid content"
delimited size into isbn-message
when other
string isbn-work " valid ISBN"
delimited size into isbn-message
end-evaluate
.
checkSUM.
move 0 to weighted-sum
perform varying digit-position from 1 by 1
until digit-position > 9
compute digit-weight = (11 - digit-position)
compute weighted-sum = weighted-sum
+ (isbn-digit (digit-position) * digit-weight)
end-perform
compute weighted-sum = 11 - function mod (weighted-sum 11)
compute weighted-sum = function mod (weighted-sum 11)
if weighted-sum = 10
move "X" to check-digit
else
move weighted-sum to check-digit-9
end-if
.
Output:
1856266532 valid ISBN
0864500572 valid ISBN
0201314525 valid ISBN
159486781X valid ISBN
159486781x valid ISBN
0743287290 valid ISBN
081185213X valid ISBN
1B56266532 invalid content
159486781Z invalid checksum
1856266537 invalid checksum

Related

forvalues and xtile in Stata

What do the last two lines do? As far as I understand, these lines loop through the list h_nwave and calculate the weighted quantiles, if syear2digit == 'nwave' , i.e. calculate 5 quantiles for each year. But I'm not sure if my understanding is correct. Also is this equivalent to using group() function?
h_nwave "91 92 93 94 95 96 97 98 99 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15"
generate quantile_ip = .
forvalues number = 1(1)15 {
local nwave : word `number' of `h_nwave'
xtile quantile_ip_`nwave' = a_ip if syear2digit == `nwave' [ w = weight ], nq(5)
replace quantile_ip = quantile_ip_`nwave' if syear2digit == `nwave'
}
I try to convert this into R with forloop, mutate, xtile (statar package required) and case_when. However, so far I cannot find a suitable way to get similar result.
There is no source or context for this code.
Detail: The first command is truncated and presumably should have been
local h_nwave 91 92 93 94 95 96 97 98 99 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
Detail: The first list contains 25 values, presumably corresponding to years 1991 to 2015. But the second list implies 15 values, so we are only looking at 91 to 05.
Main idea: xtile bins to quintile bins on variable a_ip, with weights. So the lowest 20% of observations (taking weighting into account) should be in bin 1, and so on. In practice observations with the same value must be assigned to the same bin, so 20-20-20-20-20 splits are not guaranteed, quite apart from the small print of whether sample size is a multiple of 5. So, the result is assignment to bins 1 to 5, and not quintiles themselves, or any other kind quantiles.
This is done separately for each survey wave.
The xtile command is documented for everyone at https://www.stata.com/manuals/dpctile.pdf regardless of personal or workplace access to Stata.
In R, you may well be able to produce quintile bins for all survey years at once. I have no idea how to do that.
Otherwise put, the loop arises because xtile doesn't work on separate subsets in one command call. There are community-contributed Stata commands that allow that. This kind of topic is much discussed on Statalist.

convert decimal to hexidecimal 2 digits at a time Ruby

I am trying to convert a string of decimal values to hex, grabbing two digits at a time
so for example, if i were to convert these decimals two digits at a time
01 67 15 06 01 76 61 73
this would be my result
01430F06014C3D49
i know that str.to_s(16) will convert decimal to hex but like i said I need this done two digits at a time so the output is correct, and i have no clue how to do this in Ruby
here is what i have tried
str.upcase.chars.each_slice(2).to_s.(16).join
You can use String#gsub with a regular expression and Kernel#sprintf:
"01 67 15 06 01 76 61 73".gsub(/\d{2} */) { |s| sprintf("%02x", s.to_i) }
#=> "01430f06014c3d49"
The regular expression /\d{2} */) matches two digits followed by zero or more spaces (note 73 is not followed by space).
The result of the block calculation replaces the two or three characters that were matched by the regular expression.
sprintf's formatting directive forms a sting containing 2 characters, padded to the left with '0''s, if necessary, and converting the string representation of an integer in base 10 to the string representation of an integer in base 16 ('x').
Alternatively, one could use String#% (with sprintf's formatting directives):
"01 67 15 06 01 76 61 73".gsub(/\d{2} */) { |s| "%02x" % s.to_i }
#=> "01430f06014c3d49"

Identify characters in a series by a line number

this is my first time on here. I searched and couldn't find anything relevant. Trying to work something out:
Where a=1, b=2, c=3 ... z=26
If you were to create a series where it goes through every possible outcome of letters and using 1 character length in numerical order, the total possible number of outcomes is 26 (26^1). You easily figure "e" would be on line 5 of the series. "y" would be line 25.
If you set the parameters to a 2 character length, the total number of combinations is 676 (26^2), "aa" would be line 1, "az" would be line 26, "ba" would be line 27, "zz" would be line 676. This is easily calculated, and can be done no matter what the character length is, you will always find what line it would be on in the series.
My question is how do you do it in reverse? Using the same parameters, 1 will obviously be "aa", 31 will be "be". How do you work out with a formula that 676 will be "zz"? 676, based on the parameters set, can only be "zz", it can't be any other set of characters. So there should be a way of calculating this, no matter how long the number is, as long as you know the parameters of the series.
If length of characters was 10, what characters would be on line 546,879,866, for example?
Is this even doable? Thanks so much in advance
It is enough to translate 546,879,866 into 26-base number. For example in bash:
echo 'obase=26 ; 546879866' | bc
01 20 00 19 03 23 00
And if your prefere 10 caracters you should fill the number from the beginning:
00 00 00 01 20 00 19 03 23 00
Just note that numeration starts from 0 which is mean a=00, b=01, … z=25.

Does anyone know how to write this algorithm in COBOL: sum array values with sum equal to X [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I have 12 accounting amounts associated with two different invoices presented in this order: -18.91, -8.48, -3654.47, -3379.07, -648.91, -2025.30, -1121.27, -1132.26, -22.68, -324.45, -861.50, and -24.31; of these, which combination of these accounting amounts equals the invoice amount -8574.87; the remaining amounts should equal the other invoice amount of -4646.74. Is there a mathematical formula that could be written in COBOL to determine this result.
01 Acct-table value spaces.
05 acct-entry occurs 50 times
10 acct-amt PIC S9(12)V99
01 invoice-table value spaces.
05 inv-entry occurs 50 times
10 inv-amt PIC S9(12)V99
There are 2^12 different combinations of accounts. Bruteforce approach
of all combinations can be implemented using bitmask.(COBOL implemention
would be not that straight forward), An approximate pseudo code
to get the overview as follows.
for b=0 to 2^12 - 1 do #each combination
sum1 = 0
sum2= 0
for i=0 to 11 do # which elements are included
if (b && (1<<i) != 0)
sum1 = sum1+ arr[i+1]
else
sum2 = sum2+ arr[i+1]
end
end
if (sum1= -8574.87 and sum2= -4646.74)
print "got it"
end
end
Cobol implementation of it(freeformat compiled and tested using a web compiler). Bitmask /bit positions are manually calculated(by repeaedly dividing by 2 and taking remainder).
Following combination is the answer:
-8,48+-3379,07+-2025,30+-1121,27+-1132,26+-22,68+-861,50+-24,31
IDENTIFICATION DIVISION.
PROGRAM-ID. ACCSUM.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 ACCOUNTS.
05 ACC-X PIC S9(4)v99 COMP-3
OCCURS 12 TIMES.
01 B PIC 9(06) comp.
01 bx PIC 9(06) comp.
01 pos pic 9(06) comp.
01 sum1 pic s9(8)v99 comp-3.
01 sum2 pic s9(8)v99 comp-3.
01 r pic 9(06) comp.
01 res pic 9(06) comp.
01 comb pic x(15) value space.
PROCEDURE DIVISION.
0000-MAIN SECTION.
PERFORM 0100-init
perform 0200-process
STOP RUN.
0100-init section.
move -18.91 to ACC-X(1)
move -8.48 to ACC-X(2)
move -3654.47 to ACC-X(3)
move -3379.07 to ACC-X(4)
move -648.91 to ACC-X(5)
move -2025.30 to ACC-X(6)
move -1121.27 to ACC-X(7)
move -1132.26 to ACC-X(8)
move -22.68 to ACC-X(9)
move -324.45 to ACC-X(10)
move -861.50 to ACC-X(11)
move -24.31 to ACC-X(12)
exit.
0200-process section.
perform varying b from 0 by 1
until b>4095
compute bx=b
compute pos=0
move zero to sum1 sum2
perform until bx= zero
divide bx by 2 giving res remainder r
if ( r <> zero )
compute sum1 = sum1+ acc-x ( pos + 1 )
move '1' to comb(pos + 1 : 1)
else
compute sum2 = sum2+ acc-x ( pos + 1 )
move '0' to comb(pos + 1 : 1)
end-if
compute bx = bx / 2
add 1 to pos
end-perform
if (sum1 = -8574.87 ) then
display "combination :" comb "-Sum2=" sum2
end-if
end-perform
exit.

Finding close numbers in columns of data

I have 3 columns of data (hours of the day)
C1 C2 C3
01 05 00
05 09 06
11 11 10
16 17 14
20 22 18
I need to be able to separate this into an n by 3 matrix where the three numbers on each row are +/-2 hours away from each other. (The range of each row must be <=4)
Each value in each column can only be used once, so if there are more than one combination that uses the same number then one of the combinations is ignored.
So the final result would be:
05 05 06 (Taken from the 2nd in C1, 1st in C2 and 2nd in C3)
11 09 10 (Taken from the 3rd in C1, 2nd in C2 and 3rd in C3)
16 17 18 (Taken from the 4th in C1, 4th in C2 and 5th in C3)
The data in each column must remain in that same column in the final matrix, for example the 16 found in C1 needs to be in the first column of the final matrix.
I'm really struggling to find a way to put this into code, can you help?
I managed to get this to almost work in MATLAB, I dont think its particularly efficient or clever but it does the job.
It relies quite heavily however on the value in the first column, treating it as a midpoint.
This means that the allowed points in columns 2 and 3 must be either +/-2.
The situation where the value in C1 is a lower limit (eg. 30) and the other 2 values are up to 4 greater than the one found in C1 is classed as invalid, although it is still technically a solution.
x = [1,5,0; 5,9,6; 11,11,10; 16,17,14; 20,22,18];
d=size(x); %Get the size in of x in the form [row,col]
rows=d(1); %Number Of Rows
cols=d(2); %Number Of Columns
clear d;
y = nan(rows,cols); %nan Matrix the same size as x used for the output
a = ones(1,cols); %Keep track of the current index in question in each column
c = 0; %Number of "matches" or rows that are valid in the output matrix
time = zeros(1,cols); %Keep track of the current values in each column
while(max(a)<rows+1) %For every row check that no index is invalid
time(1)=x(a(1),1); %Get the value in column1
b = 2; %column counter
skip=0; %Increment the counter for column 1 if this is true
while(b<cols+1&&~skip&&max(a)<rows+1) %For columns 2->cols, if we don't need to skip the value in column 1 and all the indexes are valid.
time(b)=x(a(b),b); %Get the value in column b at row a(b)
delta = time(b)-time(1); %work out the difference in value from the first column value that is selected
if(delta>2)
%Skip first column by 1
a(1)=a(1)+1; %Increment the counter for column 1
skip=1; %Return back to the first while loop
elseif(delta<-2)
%Skip b'th column by 1
a(b)=a(b)+1; %Increment the counter for column b
else
%Its valid
if(b==cols) %If at the last column and its valid
c=c+1; %Increment the match counter
y(c,:)=time(1:cols); %Set the c'th row of the output to what we've found
a=a+1; %Move onto next number in column 1
skip=1; %Start all over
else %Not at last column yet
b=b+1;
end
end
end
end
Final Result:
05 05 06
11 09 10
16 17 14
20 22 18
nan nan nan
It seems although you are talking of having three columns of values, their row and column has no meaning. Effectively you have just a list of numbers: 01, 05, 00, 05, 09, 06, 11, 11, 10, 16, 17, 14, 20, 22, 18.
Then you order them: 00, 01, 05, 05, 06, 09, 10, 11, 11, 14, 16, 17, 18, 20, 22
Then you take three and look at their distances: 00, 01, 05 = bad, for 01 and 05 are too far apart.
Next number. 01, 05, 05? No. Next number. 05, 05, 06? Yes. Continue after them. 09, 10, 11? Yes. Continue after them. 11, 14, 16? No. Next number. 14, 16, 17? Yes. You found a solution:
05, 05, 06
09, 10, 11
14, 16, 17

Resources