A jagged array in Python is pretty much a list of lists as you mentioned.

I would use a dictionary to store the counselors activity information, where the key is the name of the counselor, and the value is the list of activities the counselor will be in charge of e.g.

counselors_activities = {"Adam": ["archery", "canoeing"],
                      "Bob": ["frisbee", "golf", "painting", "trampoline"],
                      "Carol": ["tennis", "dance", "skating"],
                      "Denise": ["cycling"],
                      "Eddie": ["horseback", "fencing", "soccer"],
                      "Fiona": ["painting"],
                      "George": ["basketball", "football"]}

And access each counselor in the dictionary as such:

counselors_activites["Adam"] # when printed will display the result => ['archery', 'canoeing']

In regards to the question, I would store the list of activities available in a list, and anytime an activity is chosen, remove it from the list and add it to the counselor in the dictionary as such:

list_of_available_activities.remove("archery")
counselors_activities["Adam"].append("archery")

And if a counselor no longer was in charge of the activity, remove it from them and add it back to the list of available activities.

Update: I have provided a more fully fledged solution below based on your requirements from your comments.

Text file, activites.txt:

Adam: archery, canoeing
Bob: frisbee, golf, painting, trampoline
Carol: tennis, dance, skating
Denise: cycling
Eddie: horseback, fencing, soccer
Fiona: painting
George: basketball, football

Code:

#Set of activities available for counselors to choose from

set_of_activities = {"archery",
                  "canoeing",
                  "frisbee",
                  "golf",
                  "painting",
                  "trampoline",
                  "tennis",
                  "dance",
                  "skating",
                  "cycling",
                  "horseback",
                  "fencing",
                  "soccer",
                  "painting",
                  "basketball",
                  "football"}

with open('activities.txt', 'r') as f:
    for line in f:

        # Iterate over the file and pull out the counselor's names
        # and insert their activities into a list

        counselor_and_activities = line.split(':')
        counselor = counselor_and_activities[0]
        activities = counselor_and_activities[1].strip().split(', ')

    # Iterate over the list of activities chosen by the counselor and
    # see if that activity is free to choose from and if the activity
    # is free to choose, remove it from the set of available activities
    # and if it is not free remove it from the counselor's activity list

    for activity in activities:
        if activity in set_of_activities:
            set_of_activities.remove(activity)
        else:
            activities.remove(activity)

    # Insert the counselor and their chosen activities into the dictionary

    counselors_activities[counselor] = activities

# print(counselors_activities)

I have made one assumption with this new example, which is that you will already have a set of activities that can be chosen from already available:

I made the text file the same format of the counselors and their activities listed in the question, but the logic can be applied to other methods of storage.

As a side note and a correction from my second example previously, I have used a set to represent the list of activities instead of a list in this example. This set will only be used to verify that no counselor will be in charge of an activity that has already been assigned to someone else; i.e., removing an activity from the set will be faster than removing an activity from the list in worst case.

The counselors can be inserted into the dictionary from the notepad file without having to insert them into a list.

When the dictionary is printed it will yield the result:

{"Adam": ["archery", "canoeing"],
 "Bob": ["frisbee", "golf", "painting", "trampoline"],
 "Carol": ["tennis", "dance", "skating"],
 "Denise": ["cycling"],
 "Eddie": ["horseback", "fencing", "soccer"],
 "Fiona": [], # Empty activity list as the painting activity was already chosen by Bob
 "George": ["basketball", "football"]}
Answer from user8605709 on Stack Overflow

A jagged array in Python is pretty much a list of lists as you mentioned.

I would use a dictionary to store the counselors activity information, where the key is the name of the counselor, and the value is the list of activities the counselor will be in charge of e.g.

counselors_activities = {"Adam": ["archery", "canoeing"],
                      "Bob": ["frisbee", "golf", "painting", "trampoline"],
                      "Carol": ["tennis", "dance", "skating"],
                      "Denise": ["cycling"],
                      "Eddie": ["horseback", "fencing", "soccer"],
                      "Fiona": ["painting"],
                      "George": ["basketball", "football"]}

And access each counselor in the dictionary as such:

counselors_activites["Adam"] # when printed will display the result => ['archery', 'canoeing']

In regards to the question, I would store the list of activities available in a list, and anytime an activity is chosen, remove it from the list and add it to the counselor in the dictionary as such:

list_of_available_activities.remove("archery")
counselors_activities["Adam"].append("archery")

And if a counselor no longer was in charge of the activity, remove it from them and add it back to the list of available activities.

Update: I have provided a more fully fledged solution below based on your requirements from your comments.

Text file, activites.txt:

Adam: archery, canoeing
Bob: frisbee, golf, painting, trampoline
Carol: tennis, dance, skating
Denise: cycling
Eddie: horseback, fencing, soccer
Fiona: painting
George: basketball, football

Code:

#Set of activities available for counselors to choose from

set_of_activities = {"archery",
                  "canoeing",
                  "frisbee",
                  "golf",
                  "painting",
                  "trampoline",
                  "tennis",
                  "dance",
                  "skating",
                  "cycling",
                  "horseback",
                  "fencing",
                  "soccer",
                  "painting",
                  "basketball",
                  "football"}

with open('activities.txt', 'r') as f:
    for line in f:

        # Iterate over the file and pull out the counselor's names
        # and insert their activities into a list

        counselor_and_activities = line.split(':')
        counselor = counselor_and_activities[0]
        activities = counselor_and_activities[1].strip().split(', ')

    # Iterate over the list of activities chosen by the counselor and
    # see if that activity is free to choose from and if the activity
    # is free to choose, remove it from the set of available activities
    # and if it is not free remove it from the counselor's activity list

    for activity in activities:
        if activity in set_of_activities:
            set_of_activities.remove(activity)
        else:
            activities.remove(activity)

    # Insert the counselor and their chosen activities into the dictionary

    counselors_activities[counselor] = activities

# print(counselors_activities)

I have made one assumption with this new example, which is that you will already have a set of activities that can be chosen from already available:

I made the text file the same format of the counselors and their activities listed in the question, but the logic can be applied to other methods of storage.

As a side note and a correction from my second example previously, I have used a set to represent the list of activities instead of a list in this example. This set will only be used to verify that no counselor will be in charge of an activity that has already been assigned to someone else; i.e., removing an activity from the set will be faster than removing an activity from the list in worst case.

The counselors can be inserted into the dictionary from the notepad file without having to insert them into a list.

When the dictionary is printed it will yield the result:

{"Adam": ["archery", "canoeing"],
 "Bob": ["frisbee", "golf", "painting", "trampoline"],
 "Carol": ["tennis", "dance", "skating"],
 "Denise": ["cycling"],
 "Eddie": ["horseback", "fencing", "soccer"],
 "Fiona": [], # Empty activity list as the painting activity was already chosen by Bob
 "George": ["basketball", "football"]}
Answer from user8605709 on Stack Overflow
Top answer
1 of 1
4

A jagged array in Python is pretty much a list of lists as you mentioned.

I would use a dictionary to store the counselors activity information, where the key is the name of the counselor, and the value is the list of activities the counselor will be in charge of e.g.

counselors_activities = {"Adam": ["archery", "canoeing"],
                      "Bob": ["frisbee", "golf", "painting", "trampoline"],
                      "Carol": ["tennis", "dance", "skating"],
                      "Denise": ["cycling"],
                      "Eddie": ["horseback", "fencing", "soccer"],
                      "Fiona": ["painting"],
                      "George": ["basketball", "football"]}

And access each counselor in the dictionary as such:

counselors_activites["Adam"] # when printed will display the result => ['archery', 'canoeing']

In regards to the question, I would store the list of activities available in a list, and anytime an activity is chosen, remove it from the list and add it to the counselor in the dictionary as such:

list_of_available_activities.remove("archery")
counselors_activities["Adam"].append("archery")

And if a counselor no longer was in charge of the activity, remove it from them and add it back to the list of available activities.

Update: I have provided a more fully fledged solution below based on your requirements from your comments.

Text file, activites.txt:

Adam: archery, canoeing
Bob: frisbee, golf, painting, trampoline
Carol: tennis, dance, skating
Denise: cycling
Eddie: horseback, fencing, soccer
Fiona: painting
George: basketball, football

Code:

#Set of activities available for counselors to choose from

set_of_activities = {"archery",
                  "canoeing",
                  "frisbee",
                  "golf",
                  "painting",
                  "trampoline",
                  "tennis",
                  "dance",
                  "skating",
                  "cycling",
                  "horseback",
                  "fencing",
                  "soccer",
                  "painting",
                  "basketball",
                  "football"}

with open('activities.txt', 'r') as f:
    for line in f:

        # Iterate over the file and pull out the counselor's names
        # and insert their activities into a list

        counselor_and_activities = line.split(':')
        counselor = counselor_and_activities[0]
        activities = counselor_and_activities[1].strip().split(', ')

    # Iterate over the list of activities chosen by the counselor and
    # see if that activity is free to choose from and if the activity
    # is free to choose, remove it from the set of available activities
    # and if it is not free remove it from the counselor's activity list

    for activity in activities:
        if activity in set_of_activities:
            set_of_activities.remove(activity)
        else:
            activities.remove(activity)

    # Insert the counselor and their chosen activities into the dictionary

    counselors_activities[counselor] = activities

# print(counselors_activities)

I have made one assumption with this new example, which is that you will already have a set of activities that can be chosen from already available:

I made the text file the same format of the counselors and their activities listed in the question, but the logic can be applied to other methods of storage.

As a side note and a correction from my second example previously, I have used a set to represent the list of activities instead of a list in this example. This set will only be used to verify that no counselor will be in charge of an activity that has already been assigned to someone else; i.e., removing an activity from the set will be faster than removing an activity from the list in worst case.

The counselors can be inserted into the dictionary from the notepad file without having to insert them into a list.

When the dictionary is printed it will yield the result:

{"Adam": ["archery", "canoeing"],
 "Bob": ["frisbee", "golf", "painting", "trampoline"],
 "Carol": ["tennis", "dance", "skating"],
 "Denise": ["cycling"],
 "Eddie": ["horseback", "fencing", "soccer"],
 "Fiona": [], # Empty activity list as the painting activity was already chosen by Bob
 "George": ["basketball", "football"]}
🌐
Wikipedia
en.wikipedia.org › wiki › Jagged_array
Jagged array - Wikipedia
March 5, 2026 - Jagged array can be implemented with Iliffe vector data structure in languages such as Java, PHP, Python (multidimensional lists), Ruby, C#.NET, Visual Basic.NET, Perl, JavaScript, Objective-C, Swift, and Atlas Autocode.
Discussions

How to make a jagged array neat in Python? - Stack Overflow
0 How to apply a function on jagged Numpy arrays (unequal row lengths) without using np.apply_along_axis()? More on stackoverflow.com
🌐 stackoverflow.com
Iterate through jagged array values and indices in Python - Stack Overflow
1 Matrix (Jagged Array) for in loop initialization More on stackoverflow.com
🌐 stackoverflow.com
April 13, 2017
python jagged array operation efficiency - Stack Overflow
I am new to Python and I am looking for the most efficient way to do operations with a jagged array. More on stackoverflow.com
🌐 stackoverflow.com
July 26, 2016
python - How to make 2D jagged array using NumPy - Stack Overflow
You can't. This is a 1d array containing lists; more like a list of lists. But what do you expect to do with such an array? ... NumPy does not support jagged arrays natively. More on stackoverflow.com
🌐 stackoverflow.com
🌐
GitHub
github.com › scikit-hep › awkward-0.x
GitHub - scikit-hep/awkward-0.x: Manipulate arrays of complex data structures as easily as Numpy. · GitHub
In fact, this is the only way to build cyclic references: an object in Python must be assigned to a name before that name can be used as a reference. Awkward Arrays are appendable, but only through AppendableArray, and Table columns may be added, changed, or removed. The only use of square-bracket assignment (i.e. __setitem__) is to modify Table columns. Awkward Arrays produced by an external program may grow continuously, as long as more deeply nested arrays are filled first. That is, the content of a JaggedArray must be updated before updating its structure arrays (starts and stops).
Starred by 214 users
Forked by 38 users
Languages: Python 63.7% | Jupyter Notebook 36.3%
🌐
Reddit
reddit.com › r/learnpython › numpy stack jagged arrays – can i make this code cleaner?
r/learnpython on Reddit: NumPy stack jagged arrays – can I make this code cleaner?
September 4, 2015 - Subreddit for posting questions and asking for general advice about your python code. ... I have 3 NumPy arrays of different lengths and want to combine them into a matrix, filling in 0s to make them equal length. I've used a rather dirty for-loop solution – is there a better way to do this? #this matrix may be jagged.
Find elsewhere
🌐
LinkedIn
linkedin.com › all topics › technology › data science › data engineering
Programming Foundations: Data Structures Online Class | LinkedIn Learning, formerly Lynda.com
While structures like arrays and queues are sometimes taken for granted, a deeper understanding is vital for any programmer who wants to know what's going on "under the hood" and understand how the choices they've made impact the performance and efficiency of their applications. In this course, Kathryn Hodge provides an in-depth overview of the most essential data structures for modern programming in Python.
Published: September 19, 2023
🌐
GitHub
github.com › topics › jagged-array
jagged-array · GitHub Topics · GitHub
December 2, 2022 - python json numpy pandas data-analysis numba apache-arrow columnar-format ragged-array cern-root rdataframe scikit-hep jagged-array
Top answer
1 of 2
3

Your array is 2x2:

In [298]: A
Out[298]: 
array([[array([1, 2, 3]), array([4, 5])],
       [array([6, 7, 8, 9]), array([10])]], dtype=object)

While A+A works, boolean tests have not been implemented for this kind of array:

In [299]: A>4
...
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

I'm going to flatten A because it makes it easier to compare with list operations:

In [301]: A1=A.flatten()

In [303]: A1+A1
Out[303]: 
array([array([2, 4, 6]), array([ 8, 10]), array([12, 14, 16, 18]),
       array([20])], dtype=object)

In [304]: [a+a for a in A1]
Out[304]: [array([2, 4, 6]), array([ 8, 10]), array([12, 14, 16, 18]), array([20])]

In [305]: timeit A1+A1
100000 loops, best of 3: 6.85 µs per loop

In [306]: timeit [a+a for a in A1]
100000 loops, best of 3: 9.09 µs per loop

The array operation is a bit faster than a list comprehension. But if I first turn the array into a list:

In [307]: A1l=A1.tolist()

In [308]: A1l
Out[308]: [array([1, 2, 3]), array([4, 5]), array([6, 7, 8, 9]), array([10])]

In [309]: timeit [a+a for a in A1l]
100000 loops, best of 3: 5.2 µs per loop

times improve. This is a good indication that the A1+A1 (or even A+A) is using a similar sort of iteration.

So the straight forward way of performing your A,B calculation is

In [310]: A2=[a[a>4] for a in A1]
In [311]: B=[a+a for a in A2]
In [312]: B
Out[312]: [array([], dtype=int32), array([10]), array([12, 14, 16, 18]), array([20])]

(we can convert to/from arrays and lists as needed).

A numpy array stores its data a flat databuffer, and uses the shape and strides attributes to quickly calculate the location of any element, regardless of the dimensions. The fast array operations use compiled code that rapidly steps though the databuffers of arguments, performing the operations element by element (or some other combination).

A dtype object array also has the flat databuffer, but the elements are pointers to lists or arrays elsewhere. So while it can index individual elements quickly, it still has to perform a Python call(s) to access the arrays. So especially when the array is 1d, it is virtually the same as a flat list with the same pointers.

Multidimensional object arrays are nicer than nested lists. You can reshape them, access elements (A[1,3] v Al[1][3]), transpose them, etc. But when it comes to iterating through all the subarrays they don't offer much of a benefit.

Looking again at your 2d array:

In [315]: timeit A+A
100000 loops, best of 3: 6.93 µs per loop  # 6.85 for A1+A1 (above)

In [316]: timeit [[j+j for j in i] for i in A]
100000 loops, best of 3: 17.1 µs per loop

In [317]: Al = A.tolist()

In [318]: timeit [[j+j for j in i] for i in Al]
100000 loops, best of 3: 7.01 µs per loop    # 5.2 for A1l flat list

Basically the same time for summing the array and iterating through the equivalent nested list.

2 of 2
0

The performance of numpy jagged array may not be optimal, but there are enough reasons to believe that it should be much better than using python nested list. As explained in your earlier post:

On principle you should have some performance bonus because every element is a numpy array. So you just need a 2 dimensional loop rather than a 3D loop (if you store every number in nested lists). Also it always saves you lots of memory allocation time to avoid using python list.

Here is a simple test:

import time,sys,random
import numpy as np
rand = np.random.rand
L = np.array([[rand(100), rand(200)],[rand(400), rand(300)]], dtype=object)
L1 = [random.random() for i in range(1000)]
arrFunc = np.vectorize(lambda x:x[x>0.3],otypes=[np.ndarray])

start = time.time()
if sys.argv[1]=='np':
  for i in range(100000):
    B=i*L
else:
  for i in range(100000):
    B=[i*x for x in L1]

end = time.time()
print ('Arithmetic Op: ', end-start)


start = time.time()
if sys.argv[1]=='np':
  for i in range(100000):
    B=arrFunc(L)
else:
  for i in range(100000):
    B=[x for x in L1 if x<0.3]
end = time.time()
print ('Indexing       ', end-start)

Result:

> python testNpJarray.py np
Arithmetic Op:  3.9719998836517334
Indexing        8.079999923706055

> python testNpJarray.py list
Arithmetic Op:  53.289000034332275
Indexing        52.10899996757507

This test may not be quite fare because the outter numpy array is quite small, you are welcome to change the size to fit into your application and tell us the results.

🌐
GitHub
github.com › scikit-hep › ragged
GitHub - scikit-hep/ragged: Manipulating ragged arrays in an Array API compliant way. · GitHub
See Awkward Array papers and presentations for more. ... ragged is a pure-Python library that only depends on awkward (which, in turn, only depends on numpy and a compiled extension). In principle (i.e.
Author: scikit-hep
🌐
Gauravpandey
gauravpandey.com › wordpress
Gauravpandey
May 8, 2012 - To iterate over a every single element of a Jagged Array in Python, you will need to override the default iterator func.
🌐
Annasguidetopython
annasguidetopython.com › python3 › data structures › arrays-creating-a-jagged-array
Arrays - Creating a jagged array in Python
May 12, 2023 - In this example, we’ve modified the second row of the jagged array by adding an element to it. In conclusion, jagged arrays are a useful data structure for dealing with varying amounts of data.
🌐
Scientific Python
discuss.scientific-python.org › contributor & development discussion
Best practices regarding accepting ragged arrays - Contributor & Development Discussion - Scientific Python
November 17, 2023 - I will repeat the question that I made on the Discord here, as I think it is better for future reference. I want to ask what is the best practice for working with ragged arrays in the presence of arbitrary array backends (because I use the Python array API standard).
Top answer
1 of 5
1

Perhaps not the most efficient but it works nicely in numpy. and will short circuit as soon as one of the conditions is False. If the first three conditions are True, we have no choice but to iterate through the rows.

Thankfull, all will shortcircuit as soon as one of the iterations is False so it won't check all the rows if it doesn't have to.

def jagged(x):
    x = np.asarray(x)
    return (
        x.dtype == "object"
        and x.ndim == 1
        and isinstance(x[0], list)
        and not all(len(row) == len(x[0]) for row in x) 
    )

If you wan't to squeeze more efficieny out, it's actually performing the len(x[0]) every iteration of the all part but this is probably inconsequential and this is a lot more legible than the alaternative which would have you write out the whole if statement.

2 of 5
0

First what you show are lists, not arrays (but more on that later):

In [305]: alist1 = [[1, 2], [3, 4, 5]]                                                   
In [306]: alist2 = [[1, 2], [3, 4], [5, 6], [[7], [8]]]                                  

Mixed len at the first level is a simple and obvious test

In [307]: [len(i) for i in alist1]                                                       
Out[307]: [2, 3]

but it's not enough with the 2nd example:

In [308]: [len(i) for i in alist2]                                                       
Out[308]: [2, 2, 2, 2]

Making an array from list1 produces a 1d object dtype:

In [310]: np.array(alist1)                                                               
Out[310]: array([list([1, 2]), list([3, 4, 5])], dtype=object)

list2 is 2d, but still object dtype:

In [311]: np.array(alist2)                                                               
Out[311]: 
array([[1, 2],
       [3, 4],
       [5, 6],
       [list([7]), list([8])]], dtype=object)

np.array is not the most efficient tool; while compiled, it does have to evaluate the nest list at least down to the level where it finds the discrepency.

If the list isn't ragged, at any level, the result is a numeric dtype:

In [321]: alist3 = [[1, 2], [3, 4], [5, 6], [7, 8]]                                      
In [322]: np.array(alist3)                                                               
Out[322]: 
array([[1, 2],
       [3, 4],
       [5, 6],
       [7, 8]])

If the list elements are arrays, there can be a further result - a broadcasting error. This is results when the first dimensions match, but the differences are in the lower level(s).

In sum, if it is already a numpy array, then object is a good indicator, especially if you were expecting a numeric dtype. If the lowest level elements might themselves be objects (other than lists) this won't help. In both the list1 and list2 cases, some or all of the lowest level elements are objects - lists.

If it's a list of lists, then recursive evaluation of the len is probably the way to go. But only time tests can prove that this is better than np.array(alist).

🌐
Navaneeth Suresh
navaneeth.net › blog › indexing-ragged-arrays-in-python
Indexing Ragged Arrays in Python • Navaneeth Suresh
June 2, 2021 - This post is going to be a small attempt from my side to understand indexing ragged arrays and making that work with Python. Indexing a data structure is accessing its elements by making memory efficient. A ragged array or a jagged array is an array of arrays which the member arrays can be ...
🌐
Procodebase
procodebase.com › article › jagged-arrays
Jagged Arrays
In Python, jagged arrays can be easily represented using lists.
🌐
GitHub
github.com › scikit-hep › awkward-0.x › blob › master › docs › classes.adoc
awkward-0.x/docs/classes.adoc at master · scikit-hep/awkward-0.x
June 21, 2022 - A JaggedArray is defined by three arrays, starts, stops, and content, which are the arguments of its constructor. Below are their single-property validity conditions. They may be generated from any Python iterable, with default types chosen in the case of empty iterables.
Author: scikit-hep