a_list.count(a_callable) ?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Ping

    #16
    Re: a_list.count(a_ callable) ?

    On 6 16 , 2 06 , "BJörn Lindqvist" <bjou...@gmail. comwrote:
    >
    Maybe you could extend count() analogous to how sort() works:
    >
    # L is a list of Person objects, each Person has a name attribute
    L.sort(key = attrgetter("nam e"))
    >
    # How many olle are there?
    print L.count("olle", key = attrgetter("nam e"))
    >
    # And index could be extended in the same way!
    # Whom has id 1234?
    print L.index(1234, key = attrgetter("id" )).name
    >
    All of these could be solved by giving Person an __eq__() method, but
    it fails when you need to search, sort or count on a different key.
    >
    --
    mvh Björn
    Wow! This jumps out of my screen! I like it very much.
    How to get the extension into the language?

    cheers,
    Ping

    p.s. By the way, I guess you meant
    print L[L.index(1234, key = attrgetter("id" ))].name
    in the index example.

    Comment

    • Wildemar Wildenburger

      #17
      Re: a_list.count(a_ callable) ?

      Ping wrote:
      On 6 16 , 2 06 , "BJörn Lindqvist" <bjou...@gmail. comwrote:
      >
      >Maybe you could extend count() analogous to how sort() works:
      >>
      >
      Wow! This jumps out of my screen! I like it very much.
      How to get the extension into the language?
      >
      Well, you subclass list and extend/override the method.

      class SmartCountingLi st(list):
      def count(self, item, func=lambda x: x):
      return len([item for item in self if func(item) is True])

      .... or whatever (this is dummy code, not tested and probably riddled
      with stupid errors --- so take this as a pseudocode example)

      /W

      Comment

      • Dustan

        #18
        Re: a_list.count(a_ callable) ?

        On Jun 16, 12:04 pm, Wildemar Wildenburger <wilde...@freak mail.de>
        wrote:
        class SmartCountingLi st(list):
        def count(self, item, func=lambda x: x):
        return len([item for item in self if func(item) is True])
        A less bug-prone and (I would think) speedier example, although still
        untested:

        class SmartCountingLi st(list):
        def count(self, item, func=lambda x: x):
        return sum(1 for i in self if func(item)==ite m)

        Then, you would call it as follows:
        a_list.count(Tr ue, a_function)

        Comment

        • Dustan

          #19
          Re: a_list.count(a_ callable) ?

          On Jun 16, 3:37 pm, Dustan <DustanGro...@g mail.comwrote:
          class SmartCountingLi st(list):
          def count(self, item, func=lambda x: x):
          return sum(1 for i in self if func(item)==ite m)
          >
          Then, you would call it as follows:
          a_list.count(Tr ue, a_function)
          I need to learn to think things through before hitting the send button
          (or test my examples); none of the mistakes I've made on this thread
          have been from ignorance.

          If a_function returns a true value other than True or the number 1
          (which are technically the same), it is not 'equal' to True. Either
          the function would return True, or the count method could be written
          differently:

          class SmartCountingLi st(list):
          def count(self, item, is_func=False):
          if is_func:
          # item being a function:
          return sum(1 for i in self if item(i))
          else:
          return super(SmartCoun tingList, self).count(ite m)

          And just to prove that it works:
          >>s = SmartCountingLi st((1,2,3))
          >>s
          [1, 2, 3]
          >>s.count(1)
          1
          >>s.count(2)
          1
          >>s.count(3)
          1
          >>s.count(4)
          0
          >>s.count(lambd a n: n<3, True)
          2

          Comment

          • Evan Klitzke

            #20
            Re: a_list.count(a_ callable) ?

            On 6/16/07, Dustan <DustanGroups@g mail.comwrote:
            On Jun 16, 3:37 pm, Dustan <DustanGro...@g mail.comwrote:
            class SmartCountingLi st(list):
            def count(self, item, func=lambda x: x):
            return sum(1 for i in self if func(item)==ite m)

            Then, you would call it as follows:
            a_list.count(Tr ue, a_function)
            >
            I need to learn to think things through before hitting the send button
            (or test my examples); none of the mistakes I've made on this thread
            have been from ignorance.
            >
            If a_function returns a true value other than True or the number 1
            (which are technically the same), it is not 'equal' to True. Either
            If you're _really_ pedantic, 1 and True are _not_ the same, and this
            can be an important distinction in some situations.
            >>1 == True
            True
            >>1 is True
            False

            --
            Evan Klitzke <evan@yelp.co m>

            Comment

            • Steven D'Aprano

              #21
              Re: a_list.count(a_ callable) ?

              On Sat, 16 Jun 2007 20:37:01 +0000, Dustan wrote:
              On Jun 16, 12:04 pm, Wildemar Wildenburger <wilde...@freak mail.de>
              wrote:
              >class SmartCountingLi st(list):
              > def count(self, item, func=lambda x: x):
              > return len([item for item in self if func(item) is True])
              >
              A less bug-prone and (I would think) speedier example, although still
              untested:
              >
              class SmartCountingLi st(list):
              def count(self, item, func=lambda x: x):
              return sum(1 for i in self if func(item)==ite m)
              >
              Then, you would call it as follows:
              a_list.count(Tr ue, a_function)

              Did you intend for the method to count the number of items where
              func(item) is item instead of true?

              Personally, I don't see any advantage to making this a subclass. I think a
              bare function would be far, far more sensible, since you could then apply
              it to any sequence or iterable.

              Here's a version that should work with any iterable. If the iterable is a
              short enough sequence, it will use filter to build an intermediate list.
              If it is too long, or if it can't predict how long the intermediate list
              will be, it falls back to code that doesn't build an intermediate list.

              (The cut-off length I have used is a wild guess. Somebody who cares more
              than me can time the various strategies tactics and work out the "best"
              length to switch from one strategy to another. Don't forget to try it in
              different versions of Python.)


              def count_items(ite rable, func=None, _cutoff=100003) :
              """Count the number of items of iterable where func(item) is a
              true value. Equivalent to len(filter(func , seq)).

              If func is None or not given, counts the number of true items.

              Will not work for iterators that do not terminate.
              """
              try:
              # Counting items in pure Python code is only
              # worthwhile if building a temporary list using
              # filter would take a long time.
              use_filter = len(iterable) < _cutoff
              except TypeError:
              # iterable has no len(), so play it safe and
              # avoid calling filter.
              use_filter = False
              if use_filter:
              # Take advantage of the fast filter built-in.
              return len(filter(func , iterable))
              else:
              n = 0
              if func is None:
              for item in iterable:
              if item: n += 1
              else:
              for item in iterable:
              if func(item): n += 1
              return n


              --
              Steven.

              Comment

              • Ping

                #22
                Re: a_list.count(a_ callable) ?

                Somehow I did not see my post sent about 10 hours ago.
                I'm posting it again. I apologize if it showed up twice.

                After seeing all the ideas tossed around, now I like
                the proposal made by BJörn Lindqvist the best, and I
                extend it further to match the sort() method:
                L.count(value, cmp=None, key=None)
                With this signature, the callable case is simply
                L.count(True, cmp=a_callable) ,
                although now a_callable must return True instead
                of anything logical equivalent. I can live with that.

                I made an implementation with subclassing and
                Carsten Haese's sum(1 ...) method, see below.
                It works fine for me. It would be great to see
                it supported by the built-in list. :)

                cheers,
                Ping

                $ cat slist.py
                #!/usr/bin/env python

                from operator import *

                class slist (list):
                def count(self, value, cmp=None, key=None):
                if not cmp and not key: return list.count(self , value)
                if not cmp: cmp = eq
                if not key: # cmp given, no key
                return sum(1 for i in self if cmp(i, value))
                # both cmp and key are given
                return sum(1 for i in self if cmp(key(i), value))

                class Person:
                def __init__(self, first_name, last_name, age, gender):
                self.first_name , self.last_name, self.age, self.gender
                = \
                first_name, last_name, age, gender

                a = slist([3, 5, 7, 3])
                print "a =", a
                print "a has", a.count(3), "3's and", a.count(4), "4's."
                print "a has", a.count(4, cmp=gt), "elements 4 and", \
                a.count(5, cmp=le), "elements <= 5."

                b = slist( [ Person("John", "Smith", 30, 'm'), Person("Claire" , "Doe",
                23, 'f'), \
                Person("John", "Black", 43, 'm'), Person("Anne", "Jolie", 50,
                'f') ] )
                print "b has", b.count("John", key=attrgetter( "first_name ")), \
                "elements with first_name == John."
                print "b has", b.count(25, cmp=le, key=attrgetter( "age")), \
                "elements with age <= 25."

                $ ./slist.py
                a = [3, 5, 7, 3]
                a has 2 3's and 0 4's.
                a has 2 elements 4 and 3 elements <= 5.
                b has 2 elements with first_name == John.
                b has 2 elements with age <= 30.

                Comment

                • Ping

                  #23
                  Re: a_list.count(a_ callable) ?

                  print "b has", b.count(25, cmp=le, key=attrgetter( "age")), \
                  "elements with age <= 25."
                  [deleted]
                  b has 2 elements with age <= 30.
                  Oops, I mixed up when copying and pasting at different times... :p
                  The output was of course

                  b has 1 elements with age <= 25.

                  Ping

                  Comment

                  • Antoon Pardon

                    #24
                    Re: a_list.count(a_ callable) ?

                    On 2007-06-15, Ping <ping.nsr.yeh@g mail.comwrote:
                    >>
                    >sum(1 for i in a_list if a_callable(i))
                    >>
                    >--
                    >Carsten Haesehttp://informixdb.sour ceforge.net
                    >
                    This works nicely but not very intuitive or readable to me.
                    >
                    First of all, the generator expression makes sense only to
                    trained eyes. Secondly, using sum(1 ...) to mean count()
                    isn't very intuitive either.
                    >
                    I would still prefer an expression like a_list.count(a_ callable),
                    which is short, clean, and easy to understand. :) However,
                    it does produce ambiguities if a_list is a list of callables.
                    Should the count() method match values or check return values
                    of a_callable? There are several possible designs but I'm not
                    sure which is better.
                    If you want to check return values, I would thing your callable argument
                    should make the call. Something like:

                    def returns_less_th an_three(func):
                    return func() < 3


                    ls.count(return s_less_than_thr ee)


                    Checking the return values implictly, would make it vey hard if not
                    impossible to check against the callables themselves if you want to.

                    --
                    Antoon Pardon

                    Comment

                    • Ping

                      #25
                      A patch to support L.count(value, cmp=None, key=None)

                      Hi,

                      I patched Objects/listobject.c to support
                      L.count(value, cmp=None, key=None).
                      I tested it with the same script above by replacing slist
                      with built-in list. It worked correctly with this small
                      test. The patch is below (126 lines, I hope that's not
                      too big to be pasted here). This is the first time that
                      I modified CPython source, and I may very well make
                      mistakes in (lack of) reference counting or other things.
                      Comments and corrections are much appreciated!

                      Regards,
                      Ping


                      --- Objects/listobject.c.or ig Sun Oct 29 05:39:10 2006
                      +++ Objects/listobject.c Tue Jun 19 01:04:30 2007
                      @@ -919,12 +919,12 @@

                      /* Comparison function. Takes care of calling a user-supplied
                      * comparison function (any callable Python object), which must not
                      be
                      - * NULL (use the ISLT macro if you don't know, or call
                      PyObject_RichCo mpareBool
                      - * with Py_LT if you know it's NULL).
                      - * Returns -1 on error, 1 if x < y, 0 if x >= y.
                      + * NULL.
                      + * Returns -9 on error, otherwise return the result of the user-
                      supplied
                      + * comparison.
                      */
                      static int
                      -islt(PyObject *x, PyObject *y, PyObject *compare)
                      +custom_compare (PyObject *x, PyObject *y, PyObject *compare)
                      {
                      PyObject *res;
                      PyObject *args;
                      @@ -936,7 +936,7 @@
                      */
                      args = PyTuple_New(2);
                      if (args == NULL)
                      - return -1;
                      + return -9;
                      Py_INCREF(x);
                      Py_INCREF(y);
                      PyTuple_SET_ITE M(args, 0, x);
                      @@ -944,16 +944,28 @@
                      res = PyObject_Call(c ompare, args, NULL);
                      Py_DECREF(args) ;
                      if (res == NULL)
                      - return -1;
                      + return -9;
                      if (!PyInt_Check(r es)) {
                      Py_DECREF(res);
                      PyErr_SetString (PyExc_TypeErro r,
                      "comparison function must return
                      int");
                      - return -1;
                      + return -9;
                      }
                      i = PyInt_AsLong(re s);
                      Py_DECREF(res);
                      - return i < 0;
                      + return i;
                      +}
                      +
                      +/* "less-than" Comparison function. Calls custom_compare to do the
                      + * actual comparison.
                      + * Returns -1 on error, 1 if x < y, 0 if x >= y.
                      + */
                      +static int
                      +islt(PyObject *x, PyObject *y, PyObject *compare)
                      +{
                      + int res = custom_compare( x, y, compare);
                      + if (res == -9) return -1;
                      + return res < 0;
                      }

                      /* If COMPARE is NULL, calls PyObject_RichCo mpareBool with Py_LT,
                      else calls
                      @@ -2232,16 +2244,44 @@
                      }

                      static PyObject *
                      -listcount(PyLis tObject *self, PyObject *v)
                      +listcount(PyLi stObject *self, PyObject * args, PyObject *kwds)
                      {
                      + PyObject *v = NULL; /* value for counting */
                      + PyObject *compare = NULL;
                      + PyObject *keyfunc = NULL;
                      + static char *kwlist[] = {"value", "cmp", "key", 0};
                      + PyObject *item;
                      Py_ssize_t count = 0;
                      Py_ssize_t i;
                      + int cmp;
                      +
                      + assert(self != NULL);
                      + assert (PyList_Check(s elf));
                      + if (args != NULL) {
                      + if (!PyArg_ParseTu pleAndKeywords( args, kwds, "O|
                      OO:count",
                      + kwlist, &v, &compare, &keyfunc))
                      + return NULL;
                      + }
                      + if (compare == Py_None)
                      + compare = NULL;
                      + if (keyfunc == Py_None)
                      + keyfunc = NULL;

                      for (i = 0; i < self->ob_size; i++) {
                      - int cmp = PyObject_RichCo mpareBool(self->ob_item[i],
                      v, Py_EQ);
                      + item = self->ob_item[i];
                      + if (keyfunc != NULL) {
                      + item = PyObject_CallFu nctionObjArgs(k eyfunc,
                      item,
                      + NULL);
                      + }
                      +
                      + if (compare != NULL) {
                      + cmp = custom_compare( item, v, compare);
                      + } else {
                      + cmp = PyObject_RichCo mpareBool(item, v,
                      Py_EQ);
                      + }
                      if (cmp 0)
                      count++;
                      - else if (cmp < 0)
                      + else if (cmp == -9)
                      return NULL;
                      }
                      return PyInt_FromSsize _t(count);
                      @@ -2404,7 +2444,7 @@
                      PyDoc_STRVAR(in dex_doc,
                      "L.index(va lue, [start, [stop]]) -integer -- return first index of
                      value");
                      PyDoc_STRVAR(co unt_doc,
                      -"L.count(va lue) -integer -- return number of occurrences of
                      value");
                      +"L.count(value , cmp=None, key=None) -integer -- return number of
                      occurrences of value [in the key] [with the cmp function].");
                      PyDoc_STRVAR(re verse_doc,
                      "L.reverse( ) -- reverse *IN PLACE*");
                      PyDoc_STRVAR(so rt_doc,
                      @@ -2422,7 +2462,7 @@
                      {"pop", (PyCFunction)li stpop, METH_VARARGS,
                      pop_doc},
                      {"remove", (PyCFunction)li stremove, METH_O, remove_doc},
                      {"index", (PyCFunction)li stindex, METH_VARARGS,
                      index_doc},
                      - {"count", (PyCFunction)li stcount, METH_O, count_doc},
                      + {"count", (PyCFunction)li stcount, METH_VARARGS |
                      METH_KEYWORDS, count_doc},
                      {"reverse", (PyCFunction)li streverse, METH_NOARGS,
                      reverse_doc},
                      {"sort", (PyCFunction)li stsort, METH_VARARGS |
                      METH_KEYWORDS, sort_doc},
                      {NULL, NULL} /* sentinel */

                      Comment

                      • =?ISO-8859-1?Q?BJ=F6rn_Lindqvist?=

                        #26
                        Re: A patch to support L.count(value, cmp=None, key=None)

                        I patched Objects/listobject.c to support
                        L.count(value, cmp=None, key=None).
                        I tested it with the same script above by replacing slist
                        with built-in list. It worked correctly with this small
                        test. The patch is below (126 lines, I hope that's not
                        Great! If you want this change included in Python, you should post it
                        on SourceForge's patch tracker at
                        http://sourceforge.net/tracker/?grou...70&atid=305470. Optionally,
                        you can also ask if people like the patch on python-dev. But IMHO, the
                        odds of this patch being accepted are slim (borrowing from the example
                        in the last thread):

                        persons.count(" olle", key = attergetter("na me"))

                        is longer and just barely more readable than

                        sum(1 for x in persons if x.name == "olle"))

                        --
                        mvh Björn

                        Comment

                        • John Machin

                          #27
                          Re: A patch to support L.count(value, cmp=None, key=None)

                          On Jun 19, 5:17 am, "BJörn Lindqvist" <bjou...@gmail. comwrote:
                          >
                          persons.count(" olle", key = attergetter("na me"))
                          >
                          is longer and just barely more readable than
                          >
                          sum(1 for x in persons if x.name == "olle"))
                          >
                          The OP's proposal seems to have a very narrow focus, whereas the
                          generator approach can handle a much wider range of queries:

                          sum(1 for x in persons if x.name == "olle" and x.country == "se"))
                          sum(x.salary for x in persons if x.name == "olle"))

                          By the time one has looked up the syntax for the augmented count
                          method, remembered the existence of something like "attergette r" [sic]
                          and nutted out its spelling and usage, somebody else using generators
                          would have the job and gone to lunch :-)

                          YAGNI. Case dismissed.

                          Comment

                          Working...