g | x | w | all
Bytes Lang Time Link
020Jelly241218T192344ZUnrelate
03005AB1E241218T134840ZKevin Cr
116JavaScript Node.js241218T011801Zl4m2
358Go240131T223938Zbigyihsu
222C++160604T163727ZAlexande
163Python 2150607T020027ZJarmex
047Perl 47 Bytes150603T021813Zprimo
240Bash150603T162459ZDaniel W
042Pyth150603T135231ZJakube
038CJam150602T212710ZDennis
239Python 2150602T221104Zmbomb007
488Factor150603T042229ZGaslight
139JavaScript Spidermonkey console150603T121304ZNot that
149JavaScript ES6150603T103900Zedc65

Jelly, 20 bytes

“:(-rdḂe1v&”yṣ”,VFṠS

Try it online!

Eval to the rescue! Like several other answers, this does not correctly handle inputs where even or odd are given in uppercase; if those do have to be handled, add 2 bytes to prepend Œl. Also assumes problem numbers are always strictly positive, which seems like an eminently reasonable assumption, but is never actually stated in the spec.

“          ”y           Transliterate:
 :(                     ':' to opening parenthesis (unparseable),
   -r                   '-' to dyadic inclusive range,
     dḂ                 'd' to mod 2,
       e1               'e' to a literal 1,
         v&             and 'v' to bitwise AND.
             ṣ”,        Split that on commas
                V       and eval each slice (vectorizing).
                 F      Flatten the results together
                  ṠS    and sum signs.

The transliteration turns each sub-specification in the list into a Jelly kinda-program (evaluating to the final value of its main link un-stringified, but otherwise having self-contained program semantics w.r.t. helper links) producing as many positive elements as there are problems specified, possibly with extra zeroes. For example, breaking apart the first test case:

"pg. 546: 17-19, 22, 26, pg. 548: 35-67 odd, 79, 80-86 even" becomes
"pg. 546( 17r19, 22, 26, pg. 548( 35r67 oḂḂ, 79, 80r86 1&1n"

pg. 546( 17r19
p                 Cartesian product of 0 with itself rangified (empty list).
 g.               GCD of all 0 elements of that empty product with 1/2.
    546           Unparseable nilad 546, which would print nothing if this ran,
       (          but it doesn't even run because of the unparseable token '('
pg. 546(          putting everything before it two lines up as a helper link
    546: 17r      because a preserved colon (floor div) would chain (0,2),(0,2).
                  New niladic chain:
         17       Start with 17,
           r19    and take the inclusive range from that to 19.

22    Just return 22.

26    Just return 26.

pg. 548( 35r67 oḂḂ
pg. 548(              Uncalled/ignored helper link/comment.
         35r67        Inclusive range from 35 to 67.
               o      Replace (nonexistent) zeroes in the range with 
         35     Ḃ     35 mod 2,
                 Ḃ    and take each of those results mod 2.

79    Just return 79.

80r86 1&1n
80r86         Inclusive range from 80 to 86.
      1&      Bitwise AND each element with 1.
        1n    For each result, is it not equal to 1?

I took care to minimize the number of transliterations needed by leveraging o as an almost-NOP and n as an actual part of my evenness test. It seems unlikely that this can go shorter without an entirely different approach, since e doesn't vectorize and v's concatenation of integers is highly undesired here, but maybe there's something clever that can obviate the split/flatten?

05AB1E, 30 bytes

',¡ε':¡θDáU'-¡€þŸʒÉXgÉQXõQ~]˜g

Try it online or verify all test cases.

Explanation:

',¡            '# Split the (implicit) input-string on ","
   ε            # Map over each substring in the list:
    ':¡        '#  Split the substring on (a potential) ":"
       θ        #  Pop and keep the last/only part
        D       #  Duplicate this part
         à      #  Only keep the letters of this copy
          U     #  Pop and store it in variable `X`
        '-¡    '#  Split it on (a potential) "-"
           €þ   #  For each part in this pair/singleton: only keep the digits
                #  (this removes any spaces, as well as "even"/"odd")
             Ÿ  #  Convert this pair/singleton to a ranged list
    ʒ           #  Filter this list by:
     É          #   Check if the current integer is odd
      X         #   Push string `X`
       g        #   Pop and push its length
        É       #   Check whether this is odd as well
                #   (1 if `X` was "odd"; 0 if it was "even" or "")
         Q      #   Check whether it equals the odd-check of the integer
     X          #   Push string `X` again
      õQ        #   Check whether it equals an empty string ""
          ~     #   Check if either was truthy
   ]            # Close both the inner filter and outer map
    ˜           # Flatten the list of lists of integers
     g          # Pop and push its length
                # (which is output implicitly as result)

JavaScript (Node.js), 116 bytes

f=x=>x.replace(/(\d+) *-?( *\d+)? *(o|e|,|$)/g,g=(_,a,b,c)=>a%~(c>{})-(a++-b&&g(_,a,b,c),c=='o'|c=='O')||++n,n=0)&&n

Try it online!

Go, 358 bytes

import(."fmt";."regexp";."strings")
func f(s string)(O int){C:=Contains
for _,m:=range MustCompile(`pg\.\d+:`).Split(ReplaceAll(ToLower(s)," ",""),-1){for _,r:=range Split(m,",") {if r==""{continue}
l,h,e,o:=0,0,C(r,"even"),C(r,"odd")
Sscanf(r,`%d-%d`,&l,&h)
h=max(h,l)
h++
for i:=l;i<h;i++{if e&&i%2<1{O++}else if o&&i%2>0{O++}else if !e&&!o{O++}}}}
return}

Attempt This Online!

Does some input preprocessing (to lowercase, remove spaces), then splits on the pages. After that, for each problem range (matches split on commas), add to the problem count. If it's even only count on even indexes, and the same for odds. If neither, count on each index.

C++ 226 224 222

I know I'm kinda late for the party, but this seemed like a fun problem and lack of entries using C-family languages bothered me.

So here's a C++ function using no regexp or string substitution, just some simple math:

void f(){char c;int o=0,i,j;while(cin>>c)c=='p'||c==80?cin.ignore(9,58):cin.unget(),cin>>i>>c&&c==45?cin>>j>>c&&(c=='e'||c=='o')?cin.ignore(9,44),c=='e'?i+=i&1,j+=!(j&1):(i+=!(i&1),j+=j&1),o+=(j-i)/2:o+=j-i:0,++o;cout<<o;}

Ungolfed:

void f()
{
  char c;
  int o=0,i,j;
  while(cin>>c)
    c=='p'||c==80?cin.ignore(9,58):cin.unget(),
    cin>>i>>c&&c==45?
      cin>>j>>c&&(c=='e'||c=='o')?
        cin.ignore(9,44),
        c=='e'?
          i+=i&1,j+=!(j&1)
        :(i+=!(i&1),j+=j&1),
        o+=(j-i)/2
      :o+=j-i
    :0,
    ++o;
  cout<<o;
}

I didn't say it would be readable, now did I? :) Ternary operators are hell. I tried my best to (sort of) format it, though, so I hope it helps at least a little bit.

Usage:

#include <iostream>
using namespace std;

void f(){char c;int o=0,i,j;while(cin>>c)c=='p'||c==80?cin.ignore(9,58):cin.unget(),cin>>i>>c&&c==45?cin>>j>>c&&(c=='e'||c=='o')?cin.ignore(9,44),c=='e'?i+=i&1,j+=!(j&1):(i+=!(i&1),j+=j&1),o+=(j-i)/2:o+=j-i:0,++o;cout<<o;}

int main()
{
  f();
}

Python 2 - 163 bytes:

Try it here

Input must be given inside quotes

import re
print len(eval(re.sub('([^,]+:|) *(\d+) *-? *(\d*)(?=.(.)).*?,',r'[x for x in range(\2,\3+1 if \3.0 else \2+1)if x%2!="oe".find("\4")]+',input()+',[]')))

Explanation:

The general approach is to convert the existing input into valid python, then evaluate this. Each comma separated value is converted to an array, which are then all appended together and the length gives the final result.

For example, with the input 12-15 odd,19, before evaluation the regex substitution will produce:

[x for x in range(12,15+1 if 15.0 else 12+1)if x%2!="oe".find("o")]
+[x for x in range(19,+1 if .0 else 19+1)if x%2!="oe".find("[")]
+[]

To break this down further:

Perl - 47 Bytes

#!perl -p054
{map$\+=($_%2x9^lc$')!~T,$&..$'*/\d+ ?-/}}{

Amended to pass the new test case.


Original

Perl - 36 Bytes

#!perl -p054
$\+=/\d+ ?-/*($'-$&>>/o|e/i)+1}{

Counting the shebang as 4, input is taken from stdin.


Sample Usage

$ echo pg. 546: 17-19, 22, 26, pg. 548: 35-67 odd, 79, 80-86 even | perl math-problems.pl
27

$ echo pg. 34: 1 | perl math-problems.pl
1

$ echo PG. 565: 2-5,PG.345:7 | perl math-problems.pl
5

$ echo pg. 343: 5,8,13 - 56 even,pg. 345: 34 - 78,80 | perl math-problems.pl
70

Caveats

For even/odd ranges, it is expected that at least one of the endpoints matches the parity of the range. For example, 11-19 odd, 11-20 odd, and 10-19 odd will all be correctly counted as 5, but 10-20 odd will be over-counted as 6.

Bash 344 315 306 294 262 252 242 240

IFS=,
o(){ R=0;for ((i=$1;i<=$2;R+=i++%2));do :
done
}
e(){ q=$R;o $*;((R=q-R))
}
for c in ${1,,};do
c=${c#*:}
m=${c##* }
l=${c%-*}
l=${l// }
h=${c#*-}
[[ a< $m ]]&&h=${h% *}
h=${h// }
((R=h-l+1))
eval "${m::1} $l $h"
((t+=R))
done;echo $t

I don't think I've golfed this as much as is possible but not bad for a first submission. Commented version below.

IFS=, # Setup IFS for the for loops, We want to be able to split on commas

o(){ # Odd
    R=0  # Reset the R variable

    # Increments R for each odd element in the range
    # $1-$2 inclusive
    for ((i=$1;i<=$2;R+=i++%2));do
        : # Noop command
    done
}

e(){ # Even
    # Save R, it contains the total number of elements between low
    # and high
    q=$R
    # Call Odd, This will set R
    o $*
    # Set R = total number of elements in sequence - number of odd elements.
    ((R=q-R))
}

# This lowercases the firs arg. IFS causes it to split on commas.
for c in ${1,,};do
    c=${c#*:}  # Strips the page prefix if it exists
    m=${c##* }  # Capture the odd/even suffix if it exists
    l=${c%-*}  # Capture low end of a range, Strips hyphen and anything after it
    l=${l// }  # Strips spaces
    h=${c#*-}  # Capture high end of a range, Strips up to and including hyphen

    # If we have captured odd/even in m this will trigger and strip
    # it from the high range variable.
    [[ a< $m ]]&&h=${h% *}
    h=${h// }  # Strip Spaces

    # Give R a value.
    # If we are in a range it will be the number of elements in that range.
    # If we arent l and h will be equal are R will be 1
    ((R=h-l+1))

    # Call the odd or even functions if we captured one of them in m.
    # If we didnt m will contain a number and this will cause a warning
    # to stderr but it still works.
    eval "${m::1} $l $h"

    # Add R to total
    ((t+=R))
done

# Print result
echo $t

Run the test cases:

bash math.sh "pg. 546: 17-19, 22, 26, pg. 548: 35-67 odd, 79, 80-86 even"
bash math.sh "pg. 34: 1"
bash math.sh "PG. 565: 2-5,PG.345:7"
bash math.sh "pg. 343: 5,8,13 - 56 even,pg. 345: 34 - 78,80"
bash math.sh "pg.492: 2-4 odd,7-9 even"

Depending on how I read the rules it could be possible to save another 4 bytes. If even/odd is always lowercase ${1,,} can be changed to $1

Pyth, 43 42 44 42 bytes

lsm-%R2}hKvMc-ecd\:G\-eK?}\ed}edGYc-rzZd\,

Try it online: Demonstration or Test harness

I think I can still chop one or two bytes.

Explanation

lsm-%R2}hKvMc-ecd\:G\-eK?}\ed}edGYc-rzZd\,  implicit: z = input string
                                    rzZ     convert z to lower-case
                                   -   d    remove all spaces from z
                                  c     \,  and split by ","
  m                                         map each part d to:
               cd\:                           split d by ":"
              e                               and only use the last part (removes page number)
             -     G                          remove all letters (removes odd/even)
            c       \-                        split by "-"
          vM                                  and evaluate all (one or two) numbers
         K                                    and store the result in K
       }hK            eK                      create the list [K[0], K[0]+1, ..., K[-1]]
    %R2                                       apply modulo 2 to each element
   -                                          and remove:
                         }\ed                   "e" in d (1 for in, 0 for not in)
                        ?    }edG               if d[-1] in "abcde...z" else
                                 Y              dummy value
 s                                            combine all the lists
l                                             print the length                                      

CJam, 61 58 51 48 46 43 41 38 bytes

leuS-',/{':/W>:~_2*2<~z),>1f&\,5--~}%,

Verify the test cases in the CJam interpreter.

How it works

leuS-      e# Read a line from STDIN, convert to uppercase and remove spaces.
',/        e# Split at commas.
{          e# For each chunk:
  ':/W>    e#   Split at colons and only keep the last chunk.
  :~       e#   Evaluate the string inside the array.
  _2*      e#   Copy the array, repeated twice.
  2<       e#   Keep only the first two elements.

           e#   In all possible cases, the array now contains exactly two
           e#   integers, which are equal in case of a single problem.

  ~        e#   Dump the array on the stack.
  z),      e#   Push [0 ... -N], where N is the second integer.
  >        e#   Discard the first M elements, where M is the first integer.
  1f&      e#   Replace each element by its parity.
  \,       e#   Push L, the length of the original array.

           e#   EVEN and ODD all push elements, so L is 1 for single problems,
           e#   2 for simple ranges, 5 for odd ranges and 6 for even ranges.

  5--      e#   Discard all elements equal to L - 5 from the array of parities.

           e#   This removes 0's for odd ranges, 1's for even ranges, -3's for
           e#   other ranges and -4's for single problems, thus counting only
           e#   problems of the desired parities.

  ~        e#   Dump the resulting array on the stack.
}%         e# Collect the results in an array.
,          e# Compute its length.

Python 2, 259 253 249 239 bytes

Try it here

This can probably still be golfed more.

Edit: Fixed a bug that caused mine to not work for 2-4 even as I had expected. Then made an adjustment for that fix. That fix saved me four bytes!

Edit: Now uses input() and +2 bytes for the two quotation marks the user must surround input with.

import re
c=0
for x in re.sub(r'..\..*?:','',input()).replace(' ','').split(','):
 y=x.split('-')
 if len(y)<2:c+=1
 else:
    a,b=y[0],y[1];d=int(re.sub('[A-z]','',b))-int(a)+1
    if b[-1]>':':d=d/2+d%2*(int(a)%2==ord(b[-3])%2)
    c+=d
print c

Less golfed (with comments! :D):

I hope these comments help some. I'm still not quite sure if I explained that last complex line correctly or not.

import re
def f(s):
    c=0
    l=re.sub(r'..\..*?:','',s).replace(' ','').split(',')   # remove pg #'s and split
    for x in l:
        y=x.split('-')
        if len(y)<2:                                # if not a range of numbers
            c+=1
        else:
            a,b=y[0],y[1]                           # first and second numbers in range
            d=int(re.sub('[A-z]','',b))-int(a)+1    # number of pages
            if b[-1]>':':                           # if last character is not a digit
                # half the range
                # plus 1 if odd # of pages, but only if first and last numbers in the range
                #       are the same parity
                # ord(b[-3])%2 is 0 for even (v), 1 for odd (o)
                d=d/2+(d%2)*(int(a)%2==ord(b[-3])%2)
            c+=d
    print c

Factor - 488 bytes:

USING: arrays ascii kernel math math.parser math.ranges pcre sequences ;
IN: examples.golf.homework

: c ( a -- b )
    >lower "(?:[,:]|^) *(\\d+) *(?:- *(\\d+) *(e|o)?)?" findall [
        rest [ second dup string>number swap or ] map
        dup length 1 = [ drop 1 ] [
            dup length 2 = [ first2 swap - 1 + ] [
                first3 "o" = [ [a,b] [ odd? ] count ] [
                    [a,b] [ even? ] count
                ] if
            ] if
        ] if
    ] map sum ;

JavaScript (Spidermonkey console) - 139

It's just easier to test on the command line.

for(r=/[:,] *(\d+)[- ]*(\d+)? *(o|e)?/gi,m=readline(z=0);f=r.exec(m);z+=!b||((p=b-a)%2||!c|a%2^/e/i.test(c))+p/(2-!c)|0)[,a,b,c]=f
print(z)

Ungolfed:

// any number set after "pg:" or a comma
// \1 is FROM, \2 is TO, \3 is odd/even 
r=/[:,] *(\d+)[- ]*(\d+)? *(o|e)?/gi;
m=readline();
z=0; // this is the sum.
while(f=r.exec(m)){
    [,from,to,oddEven]=f;
    if(!to) {
        z++;
    } else {
        if((to-from)%2) {
            // if to and from are not the same parity, add 1
            z++;
        } else {
            // if to and from are not the same parity...
            if(!oddEven) {
                // and we don't have a parity marker, add one
                z++;
            } else if(a%2 != /e/i.test(c)) {
                // if we do have a parity marker,
                // AND the parity of from and to matches the 
                // parity of the oddEven sign, then add 1
                z++;
            }
        }
        // then add the difference between to-from and
        // if oddEven exists, divide by two and round down
        z+=(to-from)/(oddEven?2:1)|0;
    }

}
print(z);

JavaScript (ES6), 149

Run snippet in Firefox to test

F=s=>s.replace(/([-,.:]) *(\d+) *(o?)(e?)/ig,(_,c,v,o,e)=>
  c=='-'?(t+=1+(o?(v-(r|1))>>1:e?(v-(-~r&~1))>>1:v-r),r=0)
  :c!='.'&&(t+=!!r,r=v)
,r=t=0)&&t+!!r

// Less golfed

U=s=>{
  var r = 0, // value, maybe range start
  t = 0; // total
  s.replace(/([-,.:]) *(\d+) *(o?)(e?)/ig, // execute function for each match
    (_ // full match, not used
     , c // separator char, match -:,.
     , v // numeric value
     , o // match first letter of ODD if present
     , e // match first letter of EVEN if present
    )=>
    {
      if (c == '-') // range end
      {
        t++; // in general, count range values as end - start + 1
        if (o) // found 'odd'
        {
          r = r | 1; // if range start is even, increment to next odd
          t += (v-r)>>1; // end - start / 2
        }
        else if (e) // found 'even'
        {
          r = (r+1) & ~1; // if range start is odd, increment to next even
          t += (v-r)>>1; // end - start / 2
        }
        else
        {
          t += v-r; // end - start
        }
        r = 0; // range start value was used
      }
      else if (c != '.') // ignore page numbers starting with '.'
      { 
        // if a range start was already saved, then it was a single value, count it
        if (r != 0) ++t;
        r = v; // save value as it counld be a range start
      }
    }
  )            
  if (r != 0) ++t; // unused, pending range start, was a single value
  return t
}

// TEST

out=x=>O.innerHTML+=x+'\n';

test=["pg. 546: 17-19, 22, 26, pg. 548: 35-67 odd, 79, 80-86 even",
"pg. 34: 1", "PG. 565: 2-5,PG.345:7",
"pg. 343: 5,8,13 - 56 even,pg. 345: 34 - 78,80"];

test.forEach(t=>out(t + ' --> ' + F(t)))
<pre id=O></pre>