Learn Python practically and Get Certified .

Popular Tutorials

Popular examples, reference materials, learn python interactively, dsa introduction.

  • What is an algorithm?
  • Data Structure and Types
  • Why learn DSA?
  • Asymptotic Notations
  • Master Theorem
  • Divide and Conquer Algorithm

Data Structures (I)

  • Types of Queue
  • Circular Queue
  • Priority Queue

Data Structures (II)

  • Linked List
  • Linked List Operations
  • Types of Linked List
  • Heap Data Structure
  • Fibonacci Heap
  • Decrease Key and Delete Node Operations on a Fibonacci Heap

Tree based DSA (I)

  • Tree Data Structure
  • Tree Traversal
  • Binary Tree
  • Full Binary Tree
  • Perfect Binary Tree
  • Complete Binary Tree
  • Balanced Binary Tree
  • Binary Search Tree

Tree based DSA (II)

  • Insertion in a B-tree
  • Deletion from a B-tree
  • Insertion on a B+ Tree
  • Deletion from a B+ Tree
  • Red-Black Tree
  • Red-Black Tree Insertion
  • Red-Black Tree Deletion

Graph based DSA

  • Graph Data Structure
  • Spanning Tree
  • Strongly Connected Components

Adjacency Matrix

Adjacency List

  • DFS Algorithm
  • Breadth-first Search
  • Bellman Ford's Algorithm

Sorting and Searching Algorithms

  • Bubble Sort
  • Selection Sort
  • Insertion Sort
  • Counting Sort
  • Bucket Sort
  • Linear Search
  • Binary Search

Greedy Algorithms

  • Greedy Algorithm
  • Ford-Fulkerson Algorithm
  • Dijkstra's Algorithm
  • Kruskal's Algorithm
  • Prim's Algorithm
  • Huffman Coding
  • Dynamic Programming
  • Floyd-Warshall Algorithm
  • Longest Common Sequence

Other Algorithms

  • Backtracking Algorithm
  • Rabin-Karp Algorithm

DSA Tutorials

Graph Data Stucture

Depth First Search (DFS)

Breadth first search

An adjacency list represents a graph as an array of linked lists. The index of the array represents a vertex and each element in its linked list represents the other vertices that form an edge with the vertex.

For example, we have a graph below.

A graph

We can represent this graph in the form of a linked list on a computer as shown below.

Linked list representation of the graph

Here, 0 , 1 , 2 , 3 are the vertices and each of them forms a linked list with all of its adjacent vertices. For instance, vertex 1 has two adjacent vertices 0 and 2. Therefore, 1 is linked with 0 and 2 in the figure above.

  • Pros of Adjacency List
  • An adjacency list is efficient in terms of storage because we only need to store the values for the edges. For a sparse graph with millions of vertices and edges, this can mean a lot of saved space.
  • It also helps to find all the vertices adjacent to a vertex easily.
  • Cons of Adjacency List
  • Finding the adjacent list is not quicker than the adjacency matrix because all the connected nodes must be first explored to find them.
  • Adjacency List Structure

The simplest adjacency list needs a node data structure to store a vertex and a graph data structure to organize the nodes.

We stay close to the basic definition of a graph - a collection of vertices and edges {V, E} . For simplicity, we use an unlabeled graph as opposed to a labeled one i.e. the vertices are identified by their indices 0,1,2,3.

Let's dig into the data structures at play here.

Don't let the struct node** adjLists overwhelm you.

All we are saying is we want to store a pointer to struct node* . This is because we don't know how many vertices the graph will have and so we cannot create an array of Linked Lists at compile time.

  • Adjacency List C++

It is the same structure but by using the in-built list STL data structures of C++, we make the structure a bit cleaner. We are also able to abstract the details of the implementation.

  • Adjacency List Java

We use Java Collections to store the Array of Linked Lists.

The type of LinkedList is determined by what data you want to store in it. For a labeled graph, you could store a dictionary instead of an Integer

  • Adjacency List Python

There is a reason Python gets so much love. A simple dictionary of vertices and its edges is a sufficient representation of a graph. You can make the vertex itself as complex as you want.

  • Adjacency List Code in Python, Java, and C/C++
  • Applications of Adjacency List
  • It is faster to use adjacency lists for graphs having less number of edges.

Table of Contents

  • Introduction

Sorry about that.

Related Tutorials

DS & Algorithms

If you're seeing this message, it means we're having trouble loading external resources on our website.

If you're behind a web filter, please make sure that the domains *.kastatic.org and *.kasandbox.org are unblocked.

To log in and use all the features of Khan Academy, please enable JavaScript in your browser.

Computer science theory

Course: computer science theory   >   unit 1.

  • Describing graphs

Representing graphs

  • Challenge: Store a graph

list representation meaning

Adjacency matrices

Adjacency lists, want to join the conversation.

  • Upvote Button navigates to signup page
  • Downvote Button navigates to signup page
  • Flag Button navigates to signup page

Good Answer

  • Dynamic Arrays
  • Linked Lists
  • Singly Linked Lists
  • Doubly Linked Lists
  • Queues and Circular Queues
  • Tree Data Structure
  • Tree Representation
  • Binary Trees
  • Binary Heaps
  • Priority Queues
  • Binomial Heaps
  • Binary Search Trees
  • Self-balancing Binary Search Trees
  • 2-3-4 Trees
  • Red Black Trees
  • Splay Trees
  • Randomly Built Binary Search Trees
  • Graphs and Graph Terminologies

Graph Representation: Adjacency List and Matrix

Introduction.

In the previous post , we introduced the concept of graphs. In this post, we discuss how to store them inside the computer. There are two popular data structures we use to represent graph: (i) Adjacency List and (ii) Adjacency Matrix. Depending upon the application, we use either adjacency list or adjacency matrix but most of the time people prefer using adjacency list over adjacency matrix.

Adjacency Lists

Adjacency lists are the right data structure for most applications of graphs.

Adjacency lists, in simple words, are the array of linked lists. We create an array of vertices and each entry in the array has a corresponding linked list containing the neighbors. In other words, if a vertex 1 has neighbors 2, 3, 4, the array position corresponding the vertex 1 has a linked list of 2, 3, and 4. We can use other data structures besides a linked list to store neighbors. I personally prefer to use a hash table and I am using the hash table in my implementation. You can also use balanced binary search trees as well. To store the adjacency list, we need $O(V + E)$ space as we need to store every vertex and their neighbors (edges).

To find if a vertex has a neighbor, we need to go through the linked list of the vertex. This requires $O(1 + deg(V))$ time. If we use balanced binary search trees, it becomes $O(1 + \log(deg(V))$ and using appropriately constructed hash tables, the running time lowers to $O(1)$.

Figure 1 shows the linked list representation of a directed graph.

In an undirected graph, to store an edge between vertices $A$ and $B$, we need to store $B$ in $A$’s linked list and vice versa. Figure 2 depicts this.

Adjacency Matrices

An adjacency matrix is a $V \times V$ array. It is obvious that it requires $O(V^2)$ space regardless of a number of edges. The entry in the matrix will be either 0 or 1. If there is an edge between vertices $A$ and $B$, we set the value of the corresponding cell to 1 otherwise we simply put 0. Adjacency matrices are a good choice when the graph is dense since we need $O(V^2)$ space anyway. We can easily find whether two vertices are neighbors by simply looking at the matrix. This can be done in $O(1)$ time. Figure 1 and 2 show the adjacency matrix representation of a directed and undirected graph.

Representing Weighted Graphs

We can modify the previous adjacency lists and adjacency matrices to store the weights. In the adjacency list, instead of storing the only vertex, we can store a pair of numbers one vertex and other the weight. Similarly, in the adjacency matrix, instead of just storing 1 we can store the actual weight. Figure 3 illustrates this.

The table below summarizes the operations and their running time in adjacency list and adjacency matrix.

OperationAdjacency List (Linked List)Adjacency List (Hash Table)Adjacency Matrix
Test if $uv$ is an edge (directed)$O(V)$$O(1)$$O(1)$
Test if $uv$ is an edge (undirected)$O(V)$$O(1)$$O(1)$
List $v$’s neighbor$O(V)$$O(V)$$O(V)$
List all edges$O(V + E)$$O(V + E)$$O(V^2)$
Insert edge $uv$$O(1)$$O(1)$$O(1)$

Implementation

Since I will be doing all the graph related problem using adjacency list, I present here the implementation of adjacency list only. You can find the codes in C++, Java, and Python below.


2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

// Author: Algorithm Tutor

// std::map has running time of O(log n) for dynamic set operations.
// use std::unordered_map if you want the constant time complexity

#include
#include
#include

class Graph {
private:
std::map , std::map , int> > graph;
public:
void addEdge(int u, int v, int weight = 1, int isDirected = true) {
std::map , std::map , int> >::iterator it;
it = graph.find(u);
if (it == graph.end()) {
std::map , int> edge_map;
edge_map[v] = weight;
graph[u] = edge_map;

} else {
graph[u][v] = weight;
}

if (!isDirected) {
it = graph.find(v);
if (it == graph.end()) {
std::map , int> edge_map;
edge_map[u] = weight;
graph[v] = edge_map;

} else {
graph[v][u] = weight;
}
}
}

void printGraph() {
std::map , std::map , int> >::iterator it;
std::map , int>::iterator it2;
for (it = graph.begin(); it != graph.end(); ++it) {
std::cout ;
for (it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
std::cout ;
std::cout ;
}
std::cout ::endl;
}
}
};

int main() {
Graph g;
g.addEdge(1, 2, 7, false);
g.addEdge(1, 3, 2, false);
g.addEdge(2, 3, 1, false);
g.addEdge(2, 4, 5, false);
g.addEdge(2, 5, 3, false);
g.addEdge(3, 5, 11, false);
g.addEdge(4, 5, 10, false);
g.addEdge(4, 6, 7, false);
g.addEdge(5, 6, 4, false);
g.printGraph();
return 0;
}

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

// Author: Algorithm Tutor

import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;

class Graph {
private HashMap

public Graph() {
graph = new HashMap
}

public void addEdge(int u, int v, int weight, boolean isDirected) {
if(graph.containsKey(u)) {
HashMap
edge_map.put(v, weight);
} else {
HashMap HashMap
edge_map.put(v, weight);
graph.put(u, edge_map);
}

if (isDirected == false) {
if(graph.containsKey(v)) {
HashMap
edge_map.put(u, weight);
} else {
HashMap HashMap
edge_map.put(u, weight);
graph.put(v, edge_map);
}
}
}

@Override
public String toString() {
String to_return = "";
Iterator it = graph.entrySet().iterator();
while (it.hasNext()) {
Map.Entry me = (Map.Entry)it.next();
System.out.print(me.getKey() + ": ");
HashMap
Iterator it1 = value.entrySet().iterator();
while (it1.hasNext()) {
Map.Entry me1 = (Map.Entry) it1.next();
System.out.print("(" + me1.getKey() + "," + me1.getValue() + ")");
System.out.print(" ");
}
System.out.println();
}
return to_return;
}

public static void main(String [] args) {
Graph g = new Graph();
g.addEdge(1, 2, 7, false);
g.addEdge(1, 3, 2, false);
g.addEdge(2, 3, 1, false);
g.addEdge(2, 4, 5, false);
g.addEdge(2, 5, 3, false);
g.addEdge(3, 5, 11, false);
g.addEdge(4, 5, 10, false);
g.addEdge(4, 6, 7, false);
g.addEdge(5, 6, 4, false);
System.out.println(g);
}
}

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# Author: Algorithm Tutor

import collections

class Graph:
def __init__(self):
self.graph = collections.defaultdict(dict)

def add_edge(self, u, v, weight = 1, directed = True):
self.graph[u][v] = weight
if not directed:
self.graph[v][u] = weight

def __str__(self):
to_return = ''
for vertex in self.graph:
to_return += str(vertex) + ': '
for edge in self.graph[vertex]:
to_return += '(' + str(edge) + ', ' + str(self.graph[vertex][edge]) + ')'
to_return += ' '

to_return += '\n'
return to_return

if __name__ == '__main__':
g = Graph()
g.add_edge(1, 2, 7, False)
g.add_edge(1, 3, 2, False)
g.add_edge(2, 3, 1, False)
g.add_edge(2, 4, 5, False)
g.add_edge(2, 5, 3, False)
g.add_edge(3, 5, 11, False)
g.add_edge(4, 5, 10, False)
g.add_edge(4, 6, 7, False)
g.add_edge(5, 6, 4, False)
print g
  • Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (n.d.). Introduction to algorithms (3rd ed.). The MIT Press.
  • Jeff Erickson. Algorithms (Prepublication draft). http://algorithms.wtf
  • Steven S. Skiena. 2008. The Algorithm Design Manual (2nd ed.). Springer Publishing Company, Incorporated.

Loading...

Building and Analyzing Graphs with the Adjacency List

Introduction.

Greetings! Welcome to the next stage of our journey in the "Mastering Graphs in Python" course! Up to this point, we've explored graph structures and adjacency matrices in great detail, uncovering the mechanics behind these critical data structures. In today's session, we'll delve into another essential graph representation: the adjacency list .

Consider your friends list on a social networking site like Facebook; this can be viewed as a classic example of an adjacency list. Each person on Facebook has a list of connections (or friends), and you can discover mutual connections by examining the overlap in your friends lists. That's precisely how adjacency lists function!

The adjacency list representation is generally more space-efficient for storing sparse graphs compared to adjacency matrices. We'll begin by theoretically understanding adjacency lists and then illustrate how to implement them in Python. We'll then learn how to perform basic operations. To put theory into practice, we'll simulate a real scenario: building a social network graph using an adjacency list. So, let's get started!

Understanding Adjacency Lists in Graph Theory

Before we dive into the implementation, let's familiarize ourselves with the concept of adjacency lists. An adjacency list simplifies a graph into its most essential and straightforward form. It's similar to creating a contacts list on your phone, where you have a compendium of everyone you can call. Likewise, in a graph, every node keeps a list, akin to a contacts list, of the nodes it's connected to.

Let's further refine our understanding with a simple example:

Suppose we have four interconnected cities shown below.

list representation meaning

Here, cities are our vertices, and roads connecting them are our edges. The adjacency list for this graph would appear as follows:

This adjacency list informs us, for instance, that San Diego is connected to San Francisco, Los Angeles, and Las Vegas - much like a city roadmap!

Creating an Adjacency List for a Graph in Python

When it comes to Python, the built-in dictionaries and lists are invaluable for representing adjacency lists.

In an adjacency list representation, dictionaries function exceptionally well. The keys represent the nodes of the graph, and the corresponding values are lists containing the adjacent nodes.

You can translate the city roadmap mentioned above into a Python dictionary as follows:

This adjacency representation is highly efficient for sparse graphs wherein the number of edges is much less than the square of the number of vertices.

Performing Basic Operations on Adjacency Lists in Python

Once we have an adjacency list, performing basic operations becomes a breeze. Since our adjacency list is essentially a dictionary of lists, adding a vertex is as simple as adding a new key-value pair to our dictionary. In the same vein, adding an edge entails merely adding a new element to the pertinent list. Ascertaining the existence of an edge is just as straightforward; all we need to do is check if a vertex exists in another vertex’s list.

Here's how these operations translate into Python code:

If we want to add a new city (let's say 'Seattle') to our roadmap, we write:

To add a new road (refer to edge in graph theory) from San Diego to Seattle, we simply need to add 'Seattle' to San Diego's list:

To verify if a road (edge) exists between San Diego and Seattle, we look up 'Seattle' in San Diego's list:

Mapping Real-world Scenarios to Theory: Building a Social Network Graph

Adjacency lists find myriad practical applications. One notable example is social networks like Facebook or LinkedIn. In such networks, each individual represents a node in the graph. When two people become friends or connections, an edge forms between their corresponding nodes.

Consider three friends: 'John', 'Emma', and 'Sam'. We can model their friendships using an adjacency list as follows:

This adjacency list tells us that John, Emma, and Sam are all friends with each other - a classic 'Friends List'!

Congratulations on your progress! You've just expanded your knowledge of graph structures by mastering adjacency lists. An adjacency list is a straightforward, no-frills method of representing a graph's structure, explicitly listing all neighbors for each vertex. It also offers the added benefit of being more space-efficient compared to adjacency matrices, especially for sparse graphs.

In summary, you've understood what adjacency lists are and how to create one for a graph using Python. You've become proficient in performing basic operations like adding a vertex, adding an edge, and checking if an edge exists. Last but certainly not least, you've delved into real-world scenarios of adjacency lists by building a social network graph.

Time to Put Theory into Practice!

Next up, we have an exciting array of hands-on exercises that will allow you to flex your skills with adjacency lists. These exercises will give you an opportunity to apply what you've learned and experience firsthand how adjacency lists are used in real-world situations. Stay tuned and gear up for some engaging exercises!

Enjoy this lesson? Now it's time to practice with Cosmo!

Adjacency List Representation of Graph

We can easily represent graphs using the following ways,

1. Adjacency matrix

2. Adjacency list

In this tutorial, we are going to see how to represent the graph using adjacency list .

Adjacency List

In Adjacency List, we use an array of a list to represent the graph.

The list size is equal to the number of vertex(n).

Let's assume the list of size n as Adjlist[n]

Adjlist[0] will have all the nodes which are connected to vertex 0.

Adjlist[1] will have all the nodes which are connected to vertex 1 and so on.

Undirected Graph

Undirected Graph

Adjacency List of Undirected Graph

Undirected Graph

Directed Graph

Directed Graph

Adjacency List of Directed Graph

Undirected Graph

list representation meaning

Adjacency List

Explore with wolfram|alpha.

WolframAlpha

More things to try:

  • Apollonian network
  • graph properties
  • angle trisection

Referenced on Wolfram|Alpha

Cite this as:.

Weisstein, Eric W. "Adjacency List." From MathWorld --A Wolfram Web Resource. https://mathworld.wolfram.com/AdjacencyList.html

Subject classifications

Learn Coding Online – CodingPanel.com

Learn about coding and technology

Represent a Graph Using a Linked List

In this article, we will learn how to represent a graph using Linked Lists . A graph is a data structure that consists of a set of vertices (nodes) and a set of pairs showing the connection between vertices. A single pair is known as an edge . Moreover, if the graph is directed, then it will be ordered. For example, (v1, v2) shows that a connection line exists from v1 to v2 and not from v2 to v1. On the contrary, if the graph is undirected, then we have unordered pairs. For example, (v1, v2) shows that an edge exists from v1 to v2 and v2 to v1 .

You can represent a graph using an Adjacency List . The Adjancey list is an array of linked lists, where the array length is equal to the number of vertices, and each array index represents a node. Moreover, an array item at index i contains a linked list containing all nodes adjacent to vertex i .

list representation meaning

Implementation of Graph Representation using Linked List

This implementation of graphs using linked lists has the following functionalities:

  • Creation: The graph can either be a Directed or an Undirected Graph . By default, it is directed.
  • Insert an edge: It takes a pair (v1, v2) and adds it to the graph. If it is a directed graph, we only add v2 in the linked list of vertex v1 located at index v1. Otherwise, we also insert an edge from v2 to v1.
  • Display the graph: It shows the complete graph represented using linked lists.

The code is given below.

Create an undirect Graph

Now call the function above to create an Undirect Graph :

Create a Direct Graph

Again, call the function to create a direct Graph

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Save my name, email, and website in this browser for the next time I comment.

Graphs and graph representations

  • vertices and edges
  • directed vs undirected graphs
  • labeled graphs
  • adjacency and degree
  • adjacency-matrix and adjacency-list representations
  • paths and cycles
  • topological sorting
  • more graph problems: shortest paths, graph coloring

A graph is a highly useful mathematical abstraction. A graph consists of a set of vertices (also called nodes ) and a set of edges (also called arcs ) connecting those vertices. There are two main kinds of graphs: undirected graphs and directed graphs . In a directed graph (sometimes abbreviated as digraph ), the edges are directed: that is, they have a direction, proceeding from a source vertex to a sink (or destination ) vertex. The sink vertex is a successor of the source, and the the source is a predecessor of the sink. In undirected graphs, the edges are symmetrical.

list representation meaning

Uses of graphs

Graphs are a highly useful abstraction in computer science because so many important problems can be expressed in terms of graphs. We have already seen a number of graph structures: for example, the objects in a running program form a directed graph in which the vertices are objects and references between objects are edges. To implement automatic garbage collection (which discards unused objects), the language implementation uses a algorithm for graph reachability .

  • states of games and puzzles, which are vertices connected by edges that are the legal moves in the game,
  • state machines, where the states are vertices and the transitions between states are edges,
  • road maps, where the vertices are intersections or points along the road and edges are roads connecting those points,
  • scheduling problems, where vertices represent events to be scheduled and edges might represent events that cannot be scheduled together, or, depending on the problem, edges that must be scheduled together,
  • and in fact, any binary relation ρ can be viewed as a directed graph in which the relationship x ρ y corresponds to an edge from vertex x to vertex y.

What is the value of having a common mathematical abstraction like graphs? One payoff is that we can develop algorithms that work on graphs in general. Once we realize we can express a problem in terms of graphs, we can consult a very large toolbox of efficient graph algorithms, rather than trying to invent a new algorithm for the specific domain of interest.

On the other hand, some problems over graphs are known to be intractable to solve in a reasonable amount of time (or at least strongly suspected to be so). If we can show that solving the problem we are given is at least as hard as solving one of these

Vertices and edges

The vertices V of a graph are a set; the edges E can be viewed as set of ordered pairs (v 1 , v 2 ) representing an edge with source vertex v 1 and sink vertex v 2 .

If the graph is undirected, then for each edge (v 1 , v 2 ), the edge set also includes (v 1 , v 2 ). Alternatively, we can view the edges in an undirected graph as a set of sets of edges {v 1 , v 2 }.

Adjacency and degree

Two vertices v and w are adjacent , written v ~ w, if they are connected by an edge. The degree of a vertex is the total number of adjacent vertices. In a directed graph, we can distinguish between outgoing and incoming edges. The out-degree of a vertex is the number of outgoing edges and the in-degree is the number of incoming edgs.

The real value of graphs is obtained when we can use them to organize information. Both edges and vertices of graphs can have labels that carry meaning about an entity represented by a vertex or about the relationship between two entities represented by an edge. For example, we might encode information about three cities, Syracuse, Ithaca, and Binghamton as edge and vertex labels in the following undirected graph:

list representation meaning

Here, the vertices are labeled with a pair containing the name of the city and its population. The edges are labeled with the distance between the cities.

A graph in which the edges are labeled with numbers is called a weighted graph . Of course, the labels do not have to represent weight; they might stand for distance betweenvertices, or the probability of transitioning from one state to another, or the similarity between two vertices, etc.

Graph representations

There is more than one way to represent a graph in a computer program. Which representation is best depend on what graphs are being represented and how they are going to be used. Let us consider the following weighted directed graph and how we might represent it:

list representation meaning

Adjacency matrix

An adjacency matrix represents a graph as a two-dimensional array. Each vertex is assigned a distinct index in [0, |V|). If the graph is represented by the 2D array m , then the edge (or lack thereof) from vertex i to vertex j is recorded at m[i][j] .

The graph structure can be represented by simplying storing a boolean value at each array index. For example, the edges in the directed graph above are represented by the true (T) values in this matrix:

  0 1 2 3
0 F T T F
1 F F F T
2 F T F T
3 F F F F

More compact bit-level representations for the booleans are also possible.

Typically there is some information associated with each edge; instead of a boolean, we store that information into the corresponding array entry:

  0 1 2 3
0 10 40
1 –5
2 25 20
3

The space required by the adjacency matrix representation is O(V 2 ), so adjacency matrices can waste a lot of space if the number of edges |E| is O(V). Such graphs are said to be sparse . For example, graphs in which in-degree or out-degree are bounded by a constant are sparse. Adjacency matrices are asymptotically space-efficient, however, when the graphs they represent are dense ; that is, |E| is O(V 2 ).

The adjacency matrix representation is time -efficient for some operations. Testing whether there is an edge between two vertices can clearly be done in constant time. However, finding all incoming edges to a given vertex, or finding all outgoing edges, takes time proportional to the number of vertices, even for sparse graphs.

Undirected graphs can be represented with an adjacency matrix too, though the matrix will be symmetrical around the matrix diagonal. This symmetry invariant makes possible some space optimizations.

Adjacency list representation

Since sparse graphs are common, the adjacency list representation is often preferred. This representation keeps track of the outgoing edges from each vertex, typically as a linked list. For example, the graph above might be represented with the following data structure:

list representation meaning

Adjacency lists are asymptotically space-efficient because they only use space proportional to the number of vertices and the number of edges. We say that they require O(V+E) space.

Finding the outgoing edges from a vertex is very efficient in the adjacency list representation too; it requires time proportional to the number of edges found. However, finding the incoming edges to a vertex is not efficient: it requires scanning the entire data structure, requiring O(V+E) time.

When it is necessary to be able to walk forward on outgoing edges and backward on incoming edges, a good approach is to maintain two adjacency lists, one representing the graph as above and one corresponding to the dual (or transposed ) graph in which all edges are reversed. That it, if there is a an edge a→b in the original graph, there is an edge b→a in the transposed graph. Of course, an invariant must be maintained between the two adjacency list representations.

Testing whether there is an edge from vertex i to vertex j requires scanning all the outgoing edges, taking O(V) time in the worse case. If this operation needs to be fast, the linked list can be replaced with a hash table. For example, we might implement the graph using this Java representation, which preserves the asympotic space efficiency of adjacency lists while also supporting queries for particular edges:

Paths and cycles

Following a series of edges from a starting vertex creates a walk through the graph, a sequence of vertices (v 0 ,...,v p ) where there is an edge from v i-1 to v i for all i between 1 and p. The length of the walk is the number of edges followed (that is, p). If no vertex appears twice in the walk, except that possibly v 0 = v n , the walk is called a path . If there are no repeating vertices, it is a simple path . If the first and last vertices are the same, the path is a cycle .

Some graphs have no cycles. For example, linked lists and trees are both examples of graphs in which there are no cycles. They are directed acyclic graphs , abbreviated as DAGs. In trees and linked lists, each vertex has at most one predecessor; in general, DAG vertices can have more than one predecessor.

Topological sorting

One use of directed graphs is to represent an ordering constraint on vertices. We use an edge from x to y to represent the idea that “x must happen before y”. A topological sort of the vertices is a total ordering of the vertices that is consistent with all edges. A graph can be topologically sorted only if it has no cycles; it must be a DAG.

Topological sorts are useful for deciding in what order to do things. For example, consider the following DAG expressing what we might call the “men's informal dressing problem”:

list representation meaning

A valid plan for getting dressed is a topological sort of this graph, and in fact any topological sort is in principle a workable way to get dressed. For example, the ordering (pants, shirt, belt, socks, tie, jacket, shoes) is consistent with the ordering on all the graph edges. Less conventional strategies are also workable, such as (socks, pants, shoes, shirt, belt, tie, jacket).

Does every DAG have a topological sort? Yes. To see this, observe that every finite DAG must have a vertex with in-degree zero. To find such a vertex, we start from an arbitrary vertex in the graph and walk backward along edges until we reach a vertex with zero in-degree. We know that the walk must generate a simple path because there are no cycles in the graph. Therefore, the walk must terminate because we run out of vertices that haven't already been seen along the walk.

This gives us an (inefficient) way to topologically sort a DAG:

Since finding the 0 in-degree node takes O(V) time, this algorithm takes O(V 2 ) time. We can do better, as we'll see shortly.

Other graph problems

Many problems of interest can be expressed in terms of graphs. Here are a few examples of important graph problems, some of which can be solved efficiently and some of which are intractable!

Reachability

One vertex is reachable from another if there is a path from one to the other. Determining which vertices are reachable from a given vertex is useful and can be done efficiently, in linear time.

Shortest paths

For example, if a road map is represented as a graph with vertices representing intersections and edges representing road segments, the shortest-path problem can be used to find short routes. There are several variants of the problem, depending on whether one is interested in the distance from a given root vertex or in the distances between all pairs of vertices. If negative-weight edges exist, these problems become harder and different algorithms (e.g., Bellman–Ford) are needed.

Hamiltonian cycles and the traveling salesman problem

The problem of finding the longest path between two nodes in a graph is, in general, intractable. It is related to some other important problems. A Hamiltonian path is one that visits every vertex in a graph. The ability to determine whether a graph contains a Hamiltonian path (or a Hamiltonian cycle) would be useful, but in general this is an intractable problem for which the best exact algorithms require exponential-time searching.

A weighted version of this problem is the traveling salesman problem (TSP), which tries to find the Hamiltonian cycle with the minimum total weight. The name comes from imagining a salesman who wants to visit every one of a set of cities while traveling the least possible total distance. This problem is at least as hard as finding Hamiltonian cycles. However, finding a solution that is within a constant factor (e.g., 1.5) of optimal can be done in polynomial time with some reasonable assumptions. In practice, there exist good heuristics that allow close-to-optimal solutions to TSP to be found even for large problem instances.

Graph coloring

Imagine that we want to schedule exams into k time slots such that no student has two exams at the same time. We can represent this problem using an undirected graph in which the exams are vertices. Exam V 1 and exam V 2 are connected by an edge if there is some student who needs to take both exams. We can schedule the exams into the k slots if there is a k-coloring of the graph: a way to assign one of k colors, representing the time slots, to each of the vertices such that no two adjacent vertices are assigned the same color.

The problem of determining whether there is a k-coloring turns out to be intractable. The chromatic number of a graph is the minimum number of colors that can be used to color it; this is of course intractable too. Though the worst case is intractable, in practice, graph colorings close to optimal can be found.

Datagy logo

  • Learn Python
  • Python Lists
  • Python Dictionaries
  • Python Strings
  • Python Functions
  • Learn Pandas & NumPy
  • Pandas Tutorials
  • Numpy Tutorials
  • Learn Data Visualization
  • Python Seaborn
  • Python Matplotlib

Representing Graphs in Python (Adjacency List and Matrix)

  • January 15, 2024 December 31, 2023

Representing Graphs in Python (Adjacency List and Matrix) Cover Image

In this tutorial, you’ll learn how to represent graphs in Python using edge lists, an adjacency matrix, and adjacency lists . While graphs can often be an intimidating data structure to learn about, they are crucial for modeling information. Graphs allow you to understand and model complex relationships, such as those in LinkedIn and Twitter (X) social networks.

By the end of this tutorial, you’ll have learned the following:

  • What graphs are and what their components, nodes, and edges, are
  • What undirected, directed, and weighted graphs are
  • How to represent graphs in Python using edge lists, adjacency lists, and adjacency matrices
  • How to convert between graph representations in Python

Table of Contents

What are Graphs, Nodes, and Edges?

In its simplest explanation, a graph is a group of dots connected by lines. Each of these dots represents things, such as people or places. The lines are used to represent the connections between things. From a technical perspective, these circles are referred to as nodes . Similarly, the lines connecting them are called edges .

For example, we could create a graph of some Facebook relationships. Let’s imagine that we have three friends: Evan, Nik, and Katie. Each of them are friends with one another. We can represent this as a graph as shown below:

This is an example of an undirected graph . This means that the relationship between two nodes occurs in both directions. Because of this, a sample edge may connect Evan and Katie. This edge would be represented by {'Evan', 'Katie'} . Note that we used a set to represent this. In this case, the relationship goes both ways.

Now let’s take a look at another example. Imagine that we’re modeling relationships in X (formerly Twitter). Because someone can follow someone without the other person following them back, we can end up with a relationship like the one shown below.

This is an example of a directed graph . Notice that the edges here are represented as arrows. In this case, Nik and Katie follow one another, but the same isn’t true for the others. Evan follows Katie (but not the other way around). Similarly, Nik follows Evan, but not the other way around.

In this case, while our nodes remain the same as {'Nik', 'Evan', 'Katie'} , our edge list now has a different meaning. In fact, it’s easier to use a list of tuples to represent this now, where the first value is the starting node and the second is the end node. This would look like this: [('Nik', 'Katie'), ('Katie', 'Nik'), ('Evan', 'Katie'), ('Nik', 'Evan')] .

Let’s now dive a little further into the different types of graphs and how they can be represented in Python.

Want to learn how to traverse these graphs? Check out my in-depth guides on breadth-first search in Python and depth-first search in Python .

Understanding Graph Data Structure Representations

In this tutorial, we’ll explore three of the most fundamental ways in which to represent graphs in Python. We’ll also explore the benefits and drawbacks of each of these representations. Later, we’ll dive into how to implement these for different types of graphs and how to create these in Python.

One of the ways you have already seen is called an edge list . The edge list lists our each of the node-pair relationships in a graph. The list can include additional information, such as weights, as you’ll see later on in this tutorial.

In Python, edge lists are often represented as lists of tuples . Let’s take a look at an example:

In the code block above, we can see a sample edge list. In this case, each pair represents an unweighted relationship (since no weight is given) between two nodes. In many cases, edge lists for undirected graphs omit the reverse pair (so ('A', 'B') represents both sides of the relationship, for example).

Another common graph representation is the adjacency matrix . Adjacency matrices are often represented as lists of lists, where each list represents a node. The list’s items represent whether an edge exists between the node and another node.

Let’s take a look at what this may look like:

In the example above, we can see all the node’s connections. For example, node 'A' comes first. We can see that it has an edge only with node 'B' . This is represented by 1, where all other nodes are 0.

One of the key benefits of an adjacency matrix over an edge list is that we can immediately see any node’s neighbors, without needing to iterate over the list.

However, adjacency matrices are often very sparse, especially for graphs with few edges. Because of this, the preferred method is the adjacency list.

An adjacency list is a hash map that maps each node to a list of neighbors. This combines the benefits of both the edge list and the adjacency matrix, by providing a contained structure that allows you to easily see a node’s neighbors.

In Python, adjacency lists are often represented as dictionaries . Each key is the node label and the value is either a set or a list of neighbors. Let’s take a look at an example:

Let’s quickly summarize the three main structures for different graph types:

AspectUndirected GraphDirected GraphWeighted Graph
Edges are represented as or to denote the lack of direction.Edges are represented as to indicate direction from A to B.Edges are represented as to include edge weights.
List of tuples: List of tuples: List of tuples: (source, destination, weight)
Adjacency lists or matrices hold connections between nodes. Edges are bidirectional.Adjacency lists or matrices hold connections between nodes, specifying direction.Adjacency lists or matrices hold connections with associated edge weights.
Nodes are connected bidirectionally.Edges have a specific direction from one node to another.Edges have weights representing the cost or value associated.
Edges are represented as to indicate the direction from A to B. (source, destination, weight)

Now that you have a good understanding of how graphs can be represented, let’s dive into some more practical examples by exploring different graph types.

Understanding Undirected Graph Data Structures

As we saw earlier, an undirected graph has edges that connect nodes without specifying direction, because the edges are bidirectional. In the edge list representation an edge connecting ('A', 'B') is the same as that connecting ('B', 'A') .

Let’s take a look at a more complete example of what this can look like. We’ll use nodes labeled A through F for this and look at different ways in which this graph can be represented.

We can see that in the image above all nodes are connected by lines without heads. This indicates that the graph is undirected and that the relationship is bidirectional. As an edge list, this graph would be represented as shown below:

In this case, it’s important to note that the graph is undirected. Without this knowledge, half the relationships would be lost. Because of this, it can sometimes be helpful to be more explicit and list out all possible relationships, as shown below:

In the code block above, we’re repeating each relationship for each node-pair relationship. While this is more explicit, it’s also redundant.

One problem with edge lists is that if we want to see any node’s neighbors, we have to iterate over the entire list. Because of this, there are different representations that we can use.

Adjacency Matrix for Undirected, Unweighted Graphs

Another common way to represent graphs is to use an adjacency matrix . In Python, these are lists of lists that store the corresponding relationship for each node. For example, our graph has seven nodes. Because of this, the adjacency matrix will have seven lists, each with seven values.

Take a look at the image below to see what our adjacency matrix for our undirected, unweighted graph looks like:

In the image above, we can see our matrix of seven rows and seven values each. The first row shows all the relationships that node 'A' has. We can see that it only connects to the second node, 'B' . In this case, edges are labelled at 1 and non-existent edges are labelled as 0.

One unique property of an adjacency matrix for undirected graphs is that they are symmetrical! If we look along the diagonal, we can see that the values are mirrored.

Let’s take a look at how we can write a function to accept an edge list and return an adjacency matrix in Python:

In the code block above, we created a function that creates an adjacency matrix out of an edge list. The function creates a set out of our nodes and then sorts that set. We then create a matrix that creates a list of lists of 0s for each node in the graph.

The function then iterates over the edges and finds the indices for each node. It adds 1 for each edge it finds. By default, the function assumes an undirected graph; if this is the case, it also adds a 1 for each reverse pair.

When we run the function on a sample edge list, we get the following code back:

Now let’s take a look at adjacency lists for undirected, unweighted graphs.

Adjacency List for Undirected, Unweighted Graphs

For graphs that don’t have many edges, adjacency matrices can become very sparse. Because of this, we can use one of the most common graph representations, the adjacency list . The adjacency list combines the benefits of both the edge list and the adjacency matrix by creating a hash map of nodes and their neighbors.

Let’s take a look at what an adjacency list would look like for an undirected, unweighted graph:

In Python, we would represent this using a dictionary. Let’s take a look at how we can create a function that accepts an edge list and returns an adjacency list:

This function is a bit simpler than our previous function. We accept both an edge list and whether our graph is directed or not. We then create a dictionary to hold our adjacency list. The iterate over each edge in our list. We check if the starting node exists in our dictionary – if it doesn’t we create it with an empty list. We then append the second node to that list.

Similarly, if the graph is not directed, we do the same thing for the inverse relationship.

Let’s take a look at what this looks like for a sample edge list:

Now that you have a strong understanding of the three representations of unweighted, undirected graphs, let’s take a look at directed graphs in Python.

Understanding Directed Graph Data Structures

Directed graphs, also known as digraphs, are networks where edges between nodes have a defined direction. Unlike undirected graphs, edges in directed graphs are unidirectional, representing a directed relationship from one node (the source) to another node (the target or destination).

Directed graphs can be helpful when modeling one-way relationships, such as one-way streets, task dependencies in project management, or social media followings.

Let’s take a look at what one of these graphs may look like:

We can see that this graph looks very similar to the one that we saw before. However, each edge has a direction on it. In this example, each edge is one-directional. However, directed graphs can also have bi-directional edges.

In this case, we can use an adjacency matrix to represent this graph as well. This is shown in the image below:

The function that we developed earlier is already built around the concept of directed graphs. In order to use it, we simply need to indicate that the graph is directed. Let’s take a look at what this looks like:

The only change that we made was in calling the function: we modified the default argument of directed=False to True . This meant that the bi-directional pair was not added to our matrix.

Similarly, we can use adjacency lists to represent directed graphs. Let’s take a look at what this would look like:

In the image above, each dictionary key is still represented by the starting node and the list of values is represented by its neighbors.

Similarly, the function we previously created allows us to pass in that we’re working with a directed graph. Let’s see what this looks like:

Similar to our previous example, we only modified the default argument of directed= to True . This allowed us to create an adjacency list for a directed graph.

Now that we’ve covered unweighted graphs, let’s dive into weighted graphs, which add additional information to our graph.

Understanding Weighted Graph Data Structures

Weighted directed graphs expand upon directed graphs by incorporating edge weights, assigning a value or cost to each directed edge between nodes. In this graph type, edges not only depict directional relationships but also carry additional information in the form of weights, indicating the strength, distance, cost, or any other relevant metric associated with the connection from one node to another.

These weights add an extra layer of complexity and significance, allowing the representation of various scenarios where the intensity or cost of relationships matters. Weighted directed graphs find applications in diverse fields such as logistics, transportation networks, telecommunications, and biology, where the weights might represent distances between locations, communication costs, strengths of interactions, or genetic similarities.

Let’s use our previous example graph and add some weights to it:

We can represent these graphs as adjacency matrices as well. In this case, rather than using the default value of 1, we represent each node by the weight that it has. Take a look at the image below for how this representation works:

To convert an edge list to an adjacency matrix using Python, we only have to make a small adjustment to our function. Let’s take a look at what this looks like:

In this case, we added a second optional parameter that indicates whether a graph is weighted or not. We use the ternary operator to assign the weight if the graph is weighted, otherwise use the default value of 1.

In an adjacency list, the keys continue to represent the nodes of the graph. The values are now lists of tuples that contain the end node and the weight for that relationship. Take a look at the image below to see what this looks like:

We can make small modifications to our earlier function to allow for weighted graphs. Let’s see what this looks like:

In the code block above, we modified our function to accept a third argument, indicating whether the graph is weighted or not. We then added a number of if-else statements to get the weight from our edge list if the edge list has weights in it.

We can see that the function returns a dictionary that contains lists of tuples, which represent the end node and the weight of the relationship.

Understanding how to represent graphs in Python is essential for anyone working with complex relationships and networks. In this tutorial, we explored three fundamental ways to represent graphs: edge lists, adjacency matrices, and adjacency lists. Each method has its strengths and trade-offs, catering to different scenarios and preferences.

We began by dissecting what graphs, nodes, and edges are, showcasing examples of undirected and directed graphs. From there, we delved into the details of each representation method:

  • Edge Lists: Simple and intuitive, representing relationships between nodes as tuples.
  • Adjacency Matrices: Efficient for determining connections between nodes but can become sparse in large, sparse graphs.
  • Adjacency Lists: Efficient for sparse graphs, offering quick access to a node’s neighbors.

We explored these representations for different graph types, including undirected, directed, and weighted graphs. For each type, we demonstrated how to convert between representations using Python functions.

Understanding these representations equips you to handle diverse scenarios, from modeling social networks to logistical networks, project management, and more. Graphs are a powerful tool for modeling relationships, and grasping their representations in Python empowers you to analyze and work with complex interconnected data effectively.

The official Python documentation also has useful information on building graphs in Python .

Nik Piepenbreier

Nik is the author of datagy.io and has over a decade of experience working with data analytics, data science, and Python. He specializes in teaching developers how to use Python for data science using hands-on tutorials. View Author posts

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Save my name, email, and website in this browser for the next time I comment.

  • Data Structures
  • Linked List
  • Binary Tree
  • Binary Search Tree
  • Segment Tree
  • Disjoint Set Union
  • Fenwick Tree
  • Red-Black Tree
  • Advanced Data Structures
  • Graph Data Structure And Algorithms
  • Introduction to Graph Data Structure

Graph and its representations

  • Types of Graphs with Examples
  • Basic Properties of a Graph
  • Applications, Advantages and Disadvantages of Graph
  • Transpose graph
  • Difference Between Graph and Tree

BFS and DFS on Graph

  • Breadth First Search or BFS for a Graph
  • Depth First Search or DFS for a Graph
  • Applications, Advantages and Disadvantages of Depth First Search (DFS)
  • Applications, Advantages and Disadvantages of Breadth First Search (BFS)
  • Iterative Depth First Traversal of Graph
  • BFS for Disconnected Graph
  • Transitive Closure of a Graph using DFS
  • Difference between BFS and DFS

Cycle in a Graph

  • Detect Cycle in a Directed Graph
  • Detect cycle in an undirected graph
  • Detect Cycle in a directed graph using colors
  • Detect a negative cycle in a Graph | (Bellman Ford)
  • Cycles of length n in an undirected and connected graph
  • Detecting negative cycle using Floyd Warshall
  • Clone a Directed Acyclic Graph

Shortest Paths in Graph

  • How to find Shortest Paths from Source to all Vertices using Dijkstra's Algorithm
  • Bellman–Ford Algorithm
  • Floyd Warshall Algorithm
  • Johnson's algorithm for All-pairs shortest paths
  • Shortest Path in Directed Acyclic Graph
  • Multistage Graph (Shortest Path)
  • Shortest path in an unweighted graph
  • Karp's minimum mean (or average) weight cycle algorithm
  • 0-1 BFS (Shortest Path in a Binary Weight Graph)
  • Find minimum weight cycle in an undirected graph

Minimum Spanning Tree in Graph

  • Kruskal’s Minimum Spanning Tree (MST) Algorithm
  • Difference between Prim's and Kruskal's algorithm for MST
  • Applications of Minimum Spanning Tree
  • Total number of Spanning Trees in a Graph
  • Minimum Product Spanning Tree
  • Reverse Delete Algorithm for Minimum Spanning Tree

Topological Sorting in Graph

  • Topological Sorting
  • All Topological Sorts of a Directed Acyclic Graph
  • Kahn's algorithm for Topological Sorting
  • Maximum edges that can be added to DAG so that it remains DAG
  • Longest Path in a Directed Acyclic Graph
  • Topological Sort of a graph using departure time of vertex

Connectivity of Graph

  • Articulation Points (or Cut Vertices) in a Graph
  • Biconnected Components
  • Bridges in a graph
  • Eulerian path and circuit for undirected graph
  • Fleury's Algorithm for printing Eulerian Path or Circuit
  • Strongly Connected Components
  • Count all possible walks from a source to a destination with exactly k edges
  • Euler Circuit in a Directed Graph
  • Word Ladder (Length of shortest chain to reach a target word)
  • Find if an array of strings can be chained to form a circle | Set 1
  • Tarjan's Algorithm to find Strongly Connected Components
  • Paths to travel each nodes using each edge (Seven Bridges of Königsberg)
  • Dynamic Connectivity | Set 1 (Incremental)

Maximum flow in a Graph

  • Max Flow Problem Introduction
  • Ford-Fulkerson Algorithm for Maximum Flow Problem
  • Find maximum number of edge disjoint paths between two vertices
  • Find minimum s-t cut in a flow network
  • Maximum Bipartite Matching
  • Channel Assignment Problem
  • Introduction to Push Relabel Algorithm
  • Introduction and implementation of Karger's algorithm for Minimum Cut
  • Dinic's algorithm for Maximum Flow

Some must do problems on Graph

  • Find size of the largest region in Boolean Matrix
  • Count number of trees in a forest
  • A Peterson Graph Problem
  • Clone an Undirected Graph
  • Introduction to Graph Coloring
  • Traveling Salesman Problem (TSP) Implementation
  • Introduction and Approximate Solution for Vertex Cover Problem
  • Erdos Renyl Model (for generating Random Graphs)
  • Chinese Postman or Route Inspection | Set 1 (introduction)
  • Hierholzer's Algorithm for directed graph
  • Boggle (Find all possible words in a board of characters) | Set 1
  • Hopcroft–Karp Algorithm for Maximum Matching | Set 1 (Introduction)
  • Construct a graph from given degrees of all vertices
  • Determine whether a universal sink exists in a directed graph
  • Number of sink nodes in a graph
  • Two Clique Problem (Check if Graph can be divided in two Cliques)

What is Graph Data Structure?

A Graph is a non-linear data structure consisting of vertices and edges. The vertices are sometimes also referred to as nodes and the edges are lines or arcs that connect any two nodes in the graph. More formally a Graph is composed of a set of vertices( V ) and a set of edges( E ). The graph is denoted by G(V, E) .

Representations of Graph

Here are the two most common ways to represent a graph :

Adjacency Matrix

Adjacency list.

An adjacency matrix is a way of representing a graph as a matrix of boolean (0’s and 1’s).

Let’s assume there are n vertices in the graph So, create a 2D matrix adjMat[n][n] having dimension n x n.

If there is an edge from vertex i to j , mark adjMat[i][j] as 1 . If there is no edge from vertex i to j , mark adjMat[i][j] as 0 .

Representation of Undirected Graph to Adjacency Matrix:

The below figure shows an undirected graph. Initially, the entire Matrix is ​​initialized to 0 . If there is an edge from source to destination, we insert 1 to both cases ( adjMat[destination] and adjMat [ destination]) because we can go either way.

Undirected_to_Adjacency_matrix

Undirected Graph to Adjacency Matrix

Representation of Directed Graph to Adjacency Matrix:

The below figure shows a directed graph. Initially, the entire Matrix is ​​initialized to 0 . If there is an edge from source to destination, we insert 1 for that particular adjMat[destination] .

Directed_to_Adjacency_matrix

Directed Graph to Adjacency Matrix

An array of Lists is used to store edges between two vertices. The size of array is equal to the number of vertices (i.e, n) . Each index in this array represents a specific vertex in the graph. The entry at the index i of the array contains a linked list containing the vertices that are adjacent to vertex i .

Let’s assume there are n vertices in the graph So, create an array of list of size n as adjList[n].

adjList[0] will have all the nodes which are connected (neighbour) to vertex 0 . adjList[1] will have all the nodes which are connected (neighbour) to vertex 1 and so on.

Representation of Undirected Graph to Adjacency list:

The below undirected graph has 3 vertices. So, an array of list will be created of size 3, where each indices represent the vertices. Now, vertex 0 has two neighbours (i.e, 1 and 2). So, insert vertex 1 and 2 at indices 0 of array. Similarly, For vertex 1, it has two neighbour (i.e, 2 and 0) So, insert vertices 2 and 0 at indices 1 of array. Similarly, for vertex 2, insert its neighbours in array of list.

Graph-Representation-of-Undirected-graph-to-Adjacency-List

Undirected Graph to Adjacency list

Representation of Directed Graph to Adjacency list:

The below directed graph has 3 vertices. So, an array of list will be created of size 3, where each indices represent the vertices. Now, vertex 0 has no neighbours. For vertex 1, it has two neighbour (i.e, 0 and 2) So, insert vertices 0 and 2 at indices 1 of array. Similarly, for vertex 2, insert its neighbours in array of list.

Graph-Representation-of-Directed-graph-to-Adjacency-List

Directed Graph to Adjacency list

Please Login to comment...

Similar reads.

  • graph-basics

Improve your Coding Skills with Practice

 alt=

What kind of Experience do you want to share?

Python's list Data Type: A Deep Dive With Examples

Python's list Data Type: A Deep Dive With Examples

Table of Contents

Getting Started With Python’s list Data Type

Creating lists through literals, using the list() constructor, building lists with list comprehensions, accessing items in a list: indexing, retrieving multiple items from a list: slicing, aliases of a list, shallow copies of a list, deep copies of a list, updating items in lists: index assignments, appending a single item at once: .append(), extending a list with multiple items at once: .extend(), inserting an item at a given position: .insert(), deleting items from a list, considering performance while growing lists, concatenating lists, repeating the content of a list, reversing a list: reversed() and .reverse(), sorting a list: sorted() and .sort(), using a for loop to iterate over a list, building new lists with comprehensions, processing lists with functional tools, finding items in a list, getting the length, maximum, and minimum of a list, comparing lists, common gotchas of python lists, subclassing the built-in list class, removing repeated items from a list, creating multidimensional lists, flattening multidimensional lists, splitting lists into chunks, using a list as a stack or queue, deciding whether to use lists.

The list class is a fundamental built-in data type in Python. It has an impressive and useful set of features, allowing you to efficiently organize and manipulate heterogeneous data. Knowing how to use lists is a must-have skill for you as a Python developer. Lists have many use cases, so you’ll frequently reach for them in real-world coding.

By working through this tutorial, you’ll dive deep into lists and get a solid understanding of their key features. This knowledge will allow you to write more effective code by taking advantage of lists.

In this tutorial, you’ll learn how to:

  • Create new lists in Python
  • Access the items in an existing list
  • Copy , update , grow , shrink , and concatenate existing lists
  • Sort , reverse , and traverse existing lists
  • Use other features of Python lists

In addition, you’ll code some examples that showcase common use cases of lists in Python. They’ll help you understand how to better use lists in your code.

To get the most out of this tutorial, you should have a good understanding of core Python concepts, including variables , functions , and for loops . You’ll also benefit from familiarity with other built-in data types , such as strings , tuples , dictionaries , and sets .

Free Bonus: Click here to download the sample code that will make you an expert on Python’s list data type.

Python’s list is a flexible, versatile, powerful, and popular built-in data type . It allows you to create variable-length and mutable sequences of objects. In a list , you can store objects of any type. You can also mix objects of different types within the same list, although list elements often share the same type.

Note: Throughout this tutorial, you’ll use the terms items , elements , and values interchangeably to refer to the objects stored in a list.

Some of the more relevant characteristics of list objects include being:

  • Ordered : They contain elements or items that are sequentially arranged according to their specific insertion order.
  • Zero-based : They allow you to access their elements by indices that start from zero.
  • Mutable : They support in-place mutations or changes to their contained elements.
  • Heterogeneous : They can store objects of different types.
  • Growable and dynamic : They can grow or shrink dynamically, which means that they support the addition, insertion, and removal of elements.
  • Nestable : They can contain other lists, so you can have lists of lists.
  • Iterable : They support iteration, so you can traverse them using a loop or comprehension while you perform operations on each of their elements.
  • Sliceable : They support slicing operations, meaning that you can extract a series of elements from them.
  • Combinable : They support concatenation operations, so you can combine two or more lists using the concatenation operators.
  • Copyable : They allow you to make copies of their content using various techniques.

Lists are sequences of objects. They’re commonly called containers or collections because a single list can contain or collect an arbitrary number of other objects.

Note: In Python, lists support a rich set of operations that are common to all sequence types, including tuples , strings, and ranges . These operations are known as common sequence operations . Throughout this tutorial, you’ll learn about several operations that fall into this category.

In Python, lists are ordered, which means that they keep their elements in the order of insertion:

The items in this list are strings representing colors. If you access the list object, then you’ll see that the colors keep the same order in which you inserted them into the list. This order remains unchanged during the list’s lifetime unless you perform some mutations on it.

You can access an individual object in a list by its position or index in the sequence. Indices start from zero:

Positions are numbered from zero to the length of the list minus one. The element at index 0 is the first element in the list, the element at index 1 is the second, and so on.

Lists can contain objects of different types. That’s why lists are heterogeneous collections:

This list contains objects of different data types, including an integer number , string, Boolean value, dictionary , tuple, and another list. Even though this feature of lists may seem cool, in practice you’ll find that lists typically store homogeneous data.

Note: One of the most relevant characteristics of lists is that they’re mutable data types. This feature deeply impacts their behavior and use cases. For example, mutability implies that lists aren’t hashable , so you can’t use them as dictionary keys . You’ll learn a lot about how mutability affects lists throughout this tutorial. So, keep reading!

Okay! That’s enough for a first glance at Python lists. In the rest of this tutorial, you’ll dive deeper into all the above characteristics of lists and more. Are you ready? To kick things off, you’ll start by learning the different ways to create lists.

Constructing Lists in Python

First things first. If you want to use a list to store or collect some data in your code, then you need to create a list object. You’ll find several ways to create lists in Python. That’s one of the features that make lists so versatile and popular.

For example, you can create lists using one of the following tools:

  • List literals
  • The list() constructor
  • A list comprehension

In the following sections, you’ll learn how to use the three tools listed above to create new lists in your code. You’ll start off with list literals.

List literals are probably the most popular way to create a list object in Python. These literals are fairly straightforward. They consist of a pair of square brackets enclosing a comma-separated series of objects.

Here’s the general syntax of a list literal:

This syntax creates a list of n items by listing the items in an enclosing pair of square brackets. Note that you don’t have to declare the items’ type or the list’s size beforehand. Remember that lists have a variable size and can store heterogeneous objects.

Here are a few examples of how to use the literal syntax to create new lists:

In these examples, you use the list literal syntax to create lists containing numbers, strings, other lists, dictionaries, and even function objects. As you already know, lists can store any type of object. They can also be empty, like the final list in the above code snippet.

Empty lists are useful in many situations. For example, maybe you want to create a list of objects resulting from computations that run in a loop. The loop will allow you to populate the empty list one element at a time.

Using a list literal is arguably the most common way to create lists. You’ll find these literals in many Python examples and codebases. They come in handy when you have a series of elements with closely related meanings, and you want to pack them into a single data structure.

Note that naming lists as plural nouns is a common practice that improves readability. However, there are situations where you can use collective nouns as well.

For example, you can have a list called people . In this case, every item will be a person . Another example would be a list that represents a table in a database. You can call the list table , and each item will be a row . You’ll find more examples like these in your walk-through of using lists.

Another tool that allows you to create list objects is the class constructor , list() . You can call this constructor with any iterable object, including other lists, tuples, sets, dictionaries and their components, strings, and many others. You can also call it without any arguments, in which case you’ll get an empty list back.

Here’s the general syntax:

To create a list, you need to call list() as you’d call any class constructor or function. Note that the square brackets around iterable mean that the argument is optional , so the brackets aren’t part of the syntax. Here are a few examples of how to use the constructor:

In these examples, you create different lists using the list() constructor, which accepts any type of iterable object, including tuples, dictionaries, strings, and many more. It even accepts sets, in which case you need to remember that sets are unordered data structures, so you won’t be able to predict the final order of items in the resulting list.

Calling list() without an argument creates and returns a new empty list. This way of creating empty lists is less common than using an empty pair of square brackets. However, in some situations, it can make your code more explicit by clearly communicating your intent: creating an empty list .

The list() constructor is especially useful when you need to create a list out of an iterator object. For example, say that you have a generator function that yields numbers from the Fibonacci sequence on demand, and you need to store the first ten numbers in a list.

In this case, you can use list() as in the code below:

Calling fibonacci_generator() directly returns a generator iterator object that allows you to iterate over the numbers in the Fibonacci sequence up to the index of your choice. However, you don’t need an iterator in your code. You need a list. A quick way to get that list is to wrap the iterator in a call to list() , as you did in the final example.

This technique comes in handy when you’re working with functions that return iterators, and you want to construct a list object out of the items that the iterator yields. The list() constructor will consume the iterator, build your list, and return it back to you.

Note: You can also use the literal syntax and the iterable unpacking operator ( * ) as an alternative to the list() constructor.

Here’s how:

In this example, the iterable unpacking operator consumes the iterator, and the square brackets build the final list of numbers. However, this technique is less readable and explicit than using list() .

As a side note, you’ll often find that built-in and third-party functions return iterators. Functions like reversed() , enumerate() , map() , and filter() are good examples of this practice. It’s less common to find functions that directly return list objects, but the built-in sorted() function is one example. It takes an iterable as an argument and returns a list of sorted items.

List comprehensions are one of the most distinctive features of Python. They’re quite popular in the Python community, so you’ll likely find them all around. List comprehensions allow you to quickly create and transform lists using a syntax that mimics a for loop but in a single line of code.

The core syntax of list comprehensions looks something like this:

Every list comprehension needs at least three components:

  • expression() is a Python expression that returns a concrete value, and most of the time, that value depends on item . Note that it doesn’t have to be a function.
  • item is the current object from iterable .
  • iterable can be any Python iterable object, such as a list , tuple , set , string , or generator .

The for construct iterates over the items in iterable , while expression(item) provides the corresponding list item that results from running the comprehension.

To illustrate how list comprehensions allow you to create new lists out of existing iterables, say that you want to construct a list with the square values of the first ten integer numbers. In this case, you can write the following comprehension:

In this example, you use range() to get the first ten integer numbers. The comprehension iterates over them while computing the square and building the new list. This example is just a quick sample of what you can do with a list comprehension.

Note: To dive deeper into list comprehensions and how to use them, check out When to Use a List Comprehension in Python .

In general, you’ll use a list comprehension when you need to create a list of transformed values out of an existing iterable. Comprehensions are a great tool that you need to master as a Python developer. They’re optimized for performance and are quick to write.

You can access individual items from a list using the item’s associated index . What’s an item’s index? Each item in a list has an index that specifies its position in the list. Indices are integer numbers that start at 0 and go up to the number of items in the list minus 1 .

To access a list item through its index, you use the following syntax:

This construct is known as an indexing operation, and the [index] part is known as the indexing operator . It consists of a pair of square brackets enclosing the desired or target index. You can read this construct as from list_object give me the item at index .

Here’s how this syntax works in practice:

Indexing your list with different indices gives you direct access to the underlying items. If you use the Big O notation for time complexity , then you can say that indexing is an O(1) operation. This means that lists are quite good for those situations where you need to access random items from a series of items.

Here’s a more visual representation of how indices map to items in a list:

In any Python list, the index of the first item is 0 , the index of the second item is 1 , and so on. The index of the last item is the number of items minus 1 .

The number of items in a list is known as the list’s length . You can check the length of a list by using the built-in len() function:

So, the index of the last item in languages is 6 - 1 = 5 . That’s the index of the "Rust" item in your sample list. If you use an index greater than this number in an indexing operation, then you get an IndexError exception:

In this example, you try to retrieve the item at index 6 . Because this index is greater than 5 , you get an IndexError as a result. Using out-of-range indices can be an incredibly common issue when you work with lists, so keep an eye on your target indices.

Indexing operations are quite flexible in Python. For example, you can also use negative indices while indexing lists. This kind of index gives you access to the list items in backward order:

A negative index specifies an element’s position relative to the right end of the list, moving back to the beginning of the list. Here’s a representation of the process:

You can access the last item in a list using index -1 . Similarly, index -2 specifies the next-to-last item, and so forth. It’s important to note that negative indices don’t start from 0 because 0 already points to the first item. This may be confusing when you’re first learning about negative and positive indices, but you’ll get used to it. It just takes a bit of practice.

Note that if you use negative indices, then -len(languages) will be the first item in the list. If you use an index lower than that value, then you get an IndexError :

When you use an index lower than -len(languages) , you get an error telling you that the target index is out of range.

Using negative indices can be very convenient in many situations. For example, accessing the last item in a list is a fairly common operation. In Python, you can do this by using negative indices, like in languages[-1] , which is more readable and concise than doing something like languages[len(languages) - 1] .

Note: Negative indices also come in handy when you need to reverse a list, as you’ll learn in the section Reversing and Sorting Lists .

As you already know, lists can contain items of any type, including other lists and sequences. When you have a list containing other sequences, you can access the items in any nested sequence by chaining indexing operations. Consider the following list of employee records:

How can you access the individual pieces of data from any given employee? You can use the following indexing syntax:

The number at the end of each index represents the level of nesting for the list. For example, your employee list has one level of nesting. Therefore, to access Alice’s data, you can do something like this:

In this example, when you do employees[1][0] , index 1 refers to the second item in the employees list. That’s a nested list containing three items. The 0 refers to the first item in that nested list, which is "Alice" . As you can see, you can access items in the nested lists by applying multiple indexing operations in a row. This technique is extensible to lists with more than one level of nesting.

If the nested items are dictionaries, then you can access their data using keys:

In this example, you have a list of dictionaries. To access the data from one of the dictionaries, you need to use its index in the list, followed by the target key in square brackets.

Another common requirement when working with lists is to extract a portion, or slice , of a given list. You can do this with a slicing operation, which has the following syntax:

The [start:stop:step] part of this construct is known as the slicing operator . Its syntax consists of a pair of square brackets and three optional indices, start , stop , and step . The second colon is optional. You typically use it only in those cases where you need a step value different from 1 .

Note: Slicing is an operation that’s common to all Python sequence data types, including lists, tuples, strings, ranges, and others.

Here’s what the indices in the slicing operator mean:

  • start specifies the index at which you want to start the slicing. The resulting slice includes the item at this index.
  • stop specifies the index at which you want the slicing to stop extracting items. The resulting slice doesn’t include the item at this index.
  • step provides an integer value representing how many items the slicing will skip on each step. The resulting slice won’t include the skipped items.

All the indices in the slicing operator are optional. They have the following default values:

Index Default Value

The minimal working variation of the indexing operator is [:] . In this variation, you rely on the default values of all the indices and take advantage of the fact that the second colon is optional. The [::] variation of the slicing operator produces the same result as [:] . This time, you rely on the default value of the three indices.

Note: Both of the above variations of the slicing operator ( [:] and [::] ) allow you to create a shallow copy of your target list. You’ll learn more about this topic in the Shallow Copies of a List section.

Now it’s time for you to explore some examples of how slicing works:

In this example, you have a list of letters in uppercase and lowercase. You want to extract the uppercase letters into one list and the lowercase letters into another list. The [0::2] operator helps you with the first task, and [1::2] helps you with the second.

In both examples, you’ve set step to 2 because you want to retrieve every other letter from the original list. In the first slicing, you use a start of 0 because you want to start from the very beginning of the list. In the second slicing, you use a start of 1 because you need to jump over the first item and start extracting items from the second one.

You can use any variation of the slicing operator that fits your needs. In many situations, relying on the default indices is pretty helpful. In the examples above, you rely on the default value of stop , which is len(list_object) . This practice allows you to run the slicing all the way up to the last item of the target list.

Here are a few more examples of slicing:

In these examples, the variable names reflect the portion of the list that you’re extracting in every slicing operation. As you can conclude, the slicing operator is pretty flexible and versatile. It even allows you to use negative indices.

Every slicing operation uses a slice object internally. The built-in slice() function provides an alternative way to create slice objects that you can use to extract multiple items from a list. The signature of this built-in function is the following:

It takes three arguments with the same meaning as the indices in the slicing operator and returns a slice object equivalent to [start:stop:step] . To illustrate how slice() works, get back to the letters example and rewrite it using this function instead of the slicing operator. You’ll end up with something like the following:

Passing None to any arguments of slice() tells the function that you want to rely on its internal default value, which is the same as the equivalent index’s default in the slicing operator. In these examples, you pass None to stop , which tells slice() that you want to use len(letters) as the value for stop .

As an exercise, you can write the digits examples using slice() instead of the slicing operator. Go ahead and give it a try! This practice will help you better understand the intricacies of slicing operations in Python.

Finally, it’s important to note that out-of-range values for start and stop don’t cause slicing expressions to raise a TypeError exception. In general, you’ll observe the following behaviors:

  • If start is before the beginning of the list, which can happen when you use negative indices, then Python will use 0 instead.
  • If start is greater than stop , then the slicing will return an empty list.
  • If stop is beyond the length of the list, then Python will use the length of the list instead.

Here are some examples that show these behaviors in action:

In these examples, your color list has seven items, so len(colors) returns 7 . In the first example, you use a negative value for start . The first item of colors is at index -7 . Because -8 < -7 , Python replaces your start value with 0 , which results in a slice that contains the items from 0 to the end of the list.

Note: In the examples above, you use only one colon in each slicing. In day-to-day coding, this is common practice. You’ll only use the second colon if you need to provide a step different from 1 . Here’s an example where step equals 2 :

In this example, you set step to 2 because you need a copy of colors that contains every other color. The slicing jumps over two colors in every step and gives you back a list of four colors.

In the second example, you use a start value that’s greater than the length of colors . Because there’s nothing to retrieve beyond the end of the list, Python returns an empty list. In the final example, you use a stop value that’s greater than the length of colors . In this case, Python retrieves all the items up to the end of the list.

Creating Copies of a List

Creating copies of an existing list is a common need in Python code. Having a copy ensures that when you change a given list, that change doesn’t affect the original data or the data in other copies.

Note: In Python, an object’s identity is a unique identifier that distinguishes it from other objects. You can use the built-in id() function to get the identity of any Python object. In Python’s CPython implementation , an object’s identity coincides with the memory address where the object is stored.

In Python, you’ll have two kinds of mechanisms to create copies of an existing list. You can create either:

  • A shallow copy
  • A deep copy

Both types of copies have specific characteristics that will directly impact their behavior. In the following sections, you’ll learn how to create shallow and deep copies of existing lists in Python. First, you’ll take a glance at aliases , a related concept that can cause some confusion and lead to issues and bugs.

In Python, you can create aliases of variables using the assignment operator ( = ). Assignments don’t create copies of objects in Python. Instead, they create bindings between the variable and the object involved in the assignment. Therefore, when you have several aliases of a given list, changes in an alias will affect the rest of the aliases.

To illustrate how you can create aliases and how they work, consider the following example:

In this code snippet, the first highlighted line creates nations as an alias of countries . Note how both variables point to the same object, which you know because the object’s identity is the same. In the second highlighted line, you update the object at index 0 in countries . This change reflects in the nations alias.

Assignment statements like the one in the first highlighted line above don’t create copies of the right-hand object. They just create aliases or variables that point to the same underlying object.

In general, aliases can come in handy in situations where you need to avoid name collisions in your code or when you need to adapt the names to specific naming patterns.

To illustrate, say that you have an app that uses your list of countries as countries in one part of the code. The app requires the same list in another part of the code, but there’s already a variable called countries with other content.

If you want both pieces of code to work on the same list, then you can use nations as an alias for countries . A handy way to do this would be to use the as keyword for creating the alias through an implicit assignment , for example, when you import the list from another module .

A shallow copy of an existing list is a new list containing references to the objects stored in the original list. In other words, when you create a shallow copy of a list, Python constructs a new list with a new identity. Then, it inserts references to the objects in the original list into the new list.

There are at least three different ways to create shallow copies of an existing list. You can use:

  • The slicing operator, [:]
  • The .copy() method
  • The copy() function from the copy module

These three tools demonstrate equivalent behavior. So, to kick things off, you’ll start exploring the slicing operator:

The highlighted line creates nations as a shallow copy of countries by using the slicing operator with one colon only. This operation takes a slice from the beginning to the end of countries . In this case, nations and countries have different identities. They’re completely independent list objects.

However, the elements in nations are aliases of the elements in countries :

As you can see, items under the same index in both nations and countries share the same object identity. This means that you don’t have copies of the items. You’re really sharing them. This behavior allows you to save some memory when working with lists and their copies.

Now, how would this impact the behavior of both lists? If you changed an item in nations , would the change reflect in countries ? The code below will help you answer these questions:

On the first line of this piece of code, you update the item at index 0 in countries . This change doesn’t affect the item at index 0 in nations . Now the first items in the lists are completely different objects with their own identities. The rest of the items, however, continue to share the same identity. So, they’re the same object in each case.

Because making copies of a list is such a common operation, the list class has a dedicated method for it. The method is called .copy() , and it returns a shallow copy of the target list:

Calling .copy() on countries gets you a shallow copy of this list. Now you have two different lists. However, their elements are common to both. Again, if you change an element in one of the lists, then the change won’t reflect in the copy.

You’ll find yet another tool for creating shallow copies of a list. The copy() function from the copy module allows you to do just that:

When you feed copy() with a mutable container data type, such as a list, the function returns a shallow copy of the input object. This copy behaves the same as the previous shallow copies that you’ve built in this section.

Sometimes you may need to build a complete copy of an existing list. In other words, you want a copy that creates a new list object and also creates new copies of the contained elements. In these situations, you’ll have to construct what’s known as a deep copy .

When you create a deep copy of a list, Python constructs a new list object and then inserts copies of the objects from the original list recursively .

To create a deep copy of an existing list, you can use the deepcopy() function from the copy module. Here’s an example of how this function works:

In this example, you create a deep copy of your matrix list. Note how both the lists and their sibling items have different identities.

Why would you need to create a deep copy of matrix , anyway? For example, if you only create a shallow copy of matrix , then you can face some issues when trying to mutate the nested lists:

In this example, you create a shallow copy of matrix . If you change items in a nested list within matrix_copy , then those changes affect the original data in matrix . The way to avoid this behavior is to use a deep copy:

Now the changes in matrix_copy or any other deep copy don’t affect the content of matrix , as you can see on the highlighted lines.

Finally, it’s important to note that when you have a list containing immutable objects, such as numbers, strings, or tuples, the behavior of deepcopy() mimics what copy() does:

In this example, even though you use deepcopy() , the items in nations are aliases of the items in countries . That behavior makes sense because you can’t change immutable objects in place. Again, this behavior optimizes the memory consumption of your code when you’re working with multiple copies of a list.

Python lists are mutable data types. This means that you can change their elements without changing the identity of the underlying list. These kinds of changes are commonly known as in-place mutations . They allow you to update the value of one or more items in an existing list.

Note: To dive deeper into what mutable and immutable data types are and how they work in Python, check out Python’s Mutable vs Immutable Types: What’s the Difference?

To change the value of a given element in a list, you can use the following syntax:

The indexing operator gives you access to the target item through its index, while the assignment operator allows you to change its current value.

Here’s how this assignment works:

In this example, you’ve replaced all the numeric values in numbers with strings. To do that, you’ve used their indices and the assignment operator in what you can call index assignments . Note that negative indices also work.

What if you know an item’s value but don’t know its index in the list? How can you update the item’s value? In this case, you can use the .index() method as in the code below:

The .index() method takes a specific item as an argument and returns the index of the first occurrence of that item in the underlying list. You can take advantage of this behavior when you know the item that you want to update but not its index. However, note that if the target item isn’t present in the list, then you’ll get a ValueError .

You can also update the value of multiple list items in one go. To do that, you can access the items with the slicing operator and then use the assignment operator and an iterable of new values. This combination of operators can be called slice assignment for short.

In this syntax, the values from iterable replace the portion of list_object defined by the slicing operator. If iterable has the same number of elements as the target slice, then Python updates the elements one by one without altering the length of list_object .

To understand these behaviors, consider the following examples:

In this example, you update the items located from 1 to 4 , without including the last item. In this slice, you have three items, so you use a list of three new values to update them one by one.

If iterable has more or fewer elements than the target slice, then list_object will automatically grow or shrink accordingly:

Now the initial list of numbers only has four values. The values 1 , 2 , and 3 are missing. So, you use a slice to insert them starting at index 1 . In this case, the slice has a single index, while the list of values has three new values, so Python grows your list automatically to hold the new values.

You can also use a slice to shrink an existing list:

Here, the initial list has a bunch of zeros where it should have a 3 . Your slicing operator takes the portion filled with zeros and replaces it with a single 3 .

Using the slicing operator to update the value of several items in an existing list is a pretty useful technique that may be hard to grasp at first. Go ahead and practice a bit more to get a deeper understanding of how this technique works.

Growing and Shrinking Lists Dynamically

In Python lists, mutability goes beyond allowing you to modify the items in place. Because lists are mutable, you can change their length on the fly by adding or removing elements. So, lists are also variable-length containers, as you already learned.

Adding new items to a list or removing unneeded ones are everyday tasks. That’s why Python provides different efficient ways to perform these actions. Using the right tool for the job is an essential skill.

In the following sections, you’ll explore the different tools that Python offers to grow and shrink a list dynamically.

The .append() method is probably the most common tool that you’ll use to add items to an existing list. As its name suggests, this method allows you to append items to a list. The method takes one item at a time and adds it to the right end of the target list.

Here’s an example of how .append() works:

In these examples, every call to .append() adds a new pet at the end of your list. This behavior allows you to gradually populate an empty list or to add items to an existing list, as you did in the example.

Note: For a deep dive into how .append() works, check out Python’s .append() : Add Items to Your Lists in Place .

Using .append() is equivalent to doing the following slice assignment:

The slice assignment in this example grows your lists by appending a new item, "hawk" , after the current last item in pets . This technique works the same as .append() . However, using .append() leads to a more readable and explicit solution.

An important fact to keep in mind when using .append() is that this method adds only a single item at a time. That item could be of any data type, including another list:

Note how the last item in pets is a list of two pets rather than two new independent pets. This behavior may be a source of subtle errors. To avoid problems, you must remember that .append() takes and adds a single item each time.

If you need to add several items from an iterable at the end of an existing list, then you can use the .extend() method, which you’ll expore in the following section.

When you’re working with lists, you may face the need to add multiple items to the right end of a list at once. Because this is such a common requirement, Python’s list has a dedicated method for that task.

The method is called .extend() . It takes an iterable of objects and appends them as individual items to the end of the target list:

The .extend() method unpacks the items in the input iterable and adds them one by one to the right end of your target list. Now fruits has three more items on its end.

You should note that .extend() can take any iterable as an argument. So, you can use tuples, strings, dictionaries and their components, iterators, and even sets. However, remember that if you use a set as an argument to extend() , then you won’t know the final order of items beforehand.

Again, you must note that .extend() is equivalent to the following slice assignment:

In this example, you use a slice assignment to add three items after the end of your original fruits list. Note that the result is the same as when you use .extend() . However, the .extend() variation is more readable and explicit.

The .insert() method is another tool that you can use to add items to an existing list. This method is a bit different from .append() and .extend() . Instead of adding items at the right end of the list, .insert() allows you to decide where you want to put your item. That said, .insert() takes two arguments:

  • index : the index at which you want to insert the item
  • item : the item that you need to insert into the list

When you insert an item at a given index, Python moves all the following items one position to the right in order to make space for the new item, which will take the place of the old item at the target index:

In this example, you insert letters into specific positions in letters . You must insert one letter at a time because .insert() adds a single item in every call. To insert an item, the method shifts all the items starting from the target index to the right end of the list. This shifting makes space for the new item.

As an exercise, could you come up with a slice assignment that produces the same result as .insert() ? Click the collapsible section below for the solution:

Slice assignment equivalent to list_object.insert(index, item) Show/Hide

Here’s the equivalent slicing of an insert operation:

This statement takes an empty slice from list_object . Why empty? Well, the slicing starts at index and stops at index . Because of this, the slice doesn’t include any items.

Then the statement assigns a one-item list to the empty slice. This action results in item being inserted at index in list_object . Go ahead and give it a try!

Now that you’ve learned how to add items to an existing list using different tools and techniques, it’s time to learn how to remove unneeded items from a list, which is another common task.

Python also allows you to remove one or more items from an existing list. Again, deleting items from lists is such a common operation that the list class already has methods to help you with that. You’ll have the following methods:

Method Description
Removes the first occurrence of from the list. It raises a if there’s no such item.
Removes the item at and returns it back to the caller. If you don’t provide a target , then removes and returns the last item in the list. Note that the square brackets around mean that the argument is optional. The brackets aren’t part of the syntax.
Removes all items from the list.

The .remove() method comes in handy when you want to remove an item from a list, but you don’t know the item’s index. If you have several items with the same value, then you can remove all of them by calling .remove() as many times as the item occurs:

The first call to .remove() deletes the first instance of the number 42 . The second call removes the remaining instance of 42 . If you call .remove() with an item that’s not in the target list, then you get a ValueError .

The .pop() method allows you to remove and return a specific item using its index. If you call the method with no index, then it removes and returns the last item in the underlying list:

In these examples, the first call to .pop() removes and returns the last site in your list of sites to visit. The second call removes and returns the first site, which is the site at index 0 .

Finally, you use .pop() with -1 as an argument to emphasize that you can also use negative indices. This call removes and returns the last item. At the end of the process, your list of sites to visit is empty, pointing out that you’ve done all your planned visits.

Removing all the items from a list in one go can be another frequent task. In this case, Python also has you covered because list has a method called .clear() , which does exactly that. Consider the following example:

If you call .clear() on a non-empty list object, then you get the list content completely removed. This method can be useful when your lists work as a cache that you want to quickly clean for a restart.

The following slice assignment produces the same result as the .clear() method:

In this slice assignment, you assign an empty list to a slice that grabs the whole target list. Again, this syntax is less explicit and readable than using .clear() .

There’s still one more Python tool that you can use to remove one or more items from an existing list. Yes, that’s the del statement . You can combine del with an indexing or slicing operation to remove an item or multiple items, respectively:

With the first del statement, you remove the color at index 1 , which is "orange" . In the second del , you use a negative index of -1 to remove the last color, "violet" . Next, you use a slice to remove "green" and "blue" .

Note: To dive deeper into using the del statement, check out Python’s del : Remove References From Scopes and Containers .

In the final example, you use del and a slice to remove all the items from an existing list. That construct produces a result that’s equivalent to calling .clear() on your target list.

When you create a list, Python allocates enough space to store the provided items. It also allocates extra space to host future items. When you use the extra space by adding new items to that list with .append() , .extend() , or .insert() , Python automatically creates room for additional new items.

This process is known as resizing , and while it ensures that the list can accept new items, it requires extra CPU time and additional memory. Why? Well, to grow an existing list, Python creates a new one with room for your current data and the extra items. Then it moves the current items to the new list and adds the new item or items.

Consider the following code to explore how Python grows a list dynamically:

In this code snippet, you first import getsizeof() from the sys module. This function allows you to get the size of an object in bytes. Then you define numbers as an empty list.

Inside the for loop, you get and print your list object’s size in bytes. The first iteration shows that the size of your empty list is 56 bytes, which is the baseline size of every list in Python.

Next, the .append() method adds a new value to your list. Note how the size of numbers grows to 88 bytes. That’s the baseline size plus 32 additional bytes ( 56 + 4 × 8 = 88 ), which represent four 8-byte pointers or slots for future items. It means that Python went ahead and allocated space for four items when you added the first element.

As the loop goes, the size of numbers grows to 120 bytes, which is 88 + 4 × 8 = 120 . This step allocates space for four more items. That’s why you get 120 four times on your screen.

If you follow the loop’s output, then you’ll notice that the next steps add room for eight extra items, then for twelve, then for sixteen, and so on. Every time Python resizes the list, it has to move all the items to the new space, which takes considerable time.

In practice, if you’re working with small lists, then the overall impact of this internal behavior is negligible. However, in performance-critical situations or when your lists are large, you may want to use more efficient data types, such as collections.deque , for example.

Check out the time complexity Wiki page for a detailed summary of how time-efficient list operations are. For example, the .append() method has a time complexity of O(1) , which means that appending an item to a list takes constant time. However, when Python has to grow the list to make room for the new item, this performance will be a bit poorer.

Being aware of the time complexity of common list operations will significantly improve your ability to choose the right tool for the job, depending on your specific needs.

Concatenating and Repeating Lists

Another interesting and useful feature of Python’s list is that it supports the following two operations:

  • Concatenation , which uses the plus operator ( + )
  • Repetition , which uses the multiplication operator ( * )

In the following sections, you’ll learn how these two operations work on Python lists and how you can use them in your code.

Concatenation consists of joining two things together. In this case, you’d like to concatenate two lists, which you can do using the plus operator ( + ). In this context, this operator is known as the concatenation operator .

Here’s how it works:

In this example, you combine three list objects using the concatenation operator. Note how the operator creates a new list containing all the individual items from the three original lists.

Whenever you use the concatenation operator, you get a new list object as a result. Consider the following example. Keep an eye on the identity of digits :

In this example, you first create a list containing a few numbers. The id() function allows you to know the identity of this first list. Then you use a concatenation operation to complete your list of digits. Note how id() now returns a different value. This result confirms that the concatenation operator always creates a new list that joins its operands.

Note: You can only concatenate a list with another list. If you try to concatenate a list with something else, then you’ll get an exception:

Python’s concatenation operator raises a TypeError exception when you try to concatenate a list with a different data type, such as a tuple.

The concatenation operator has an augmented variation , which uses the += operator. Here’s how this operator works:

The augmented concatenation operator works on an existing list. It takes a second list and adds its items, one by one, to the end of the initial list. The operation is a shortcut to something like digits = digits + [6, 7, 8, 9] . However, it works a bit differently.

Unlike the regular concatenation operator, the augmented variation mutates the target list in place rather than creating a new list:

In this example, the id() function returns the same value in both calls, meaning that you have a single list object instead of two. The augmented concatenation mutates digits in place, so the whole process is more efficient in terms of memory and execution time than a plain concatenation would be.

Repetition consists of cloning the content of a given list a specific number of times. You can achieve this with the repetition operator ( * ), which takes two operands:

  • The list whose content you want to repeat
  • The number of times that you need to repeat the content

To understand how this operator works, consider the following example:

Here, you repeat the content of a list three times and get a new list as a result. In the first example, the left-hand operand is the target list, and the right-hand operand is an integer representing the number of times that you want to repeat the list’s content. In this second example, the operands are swapped, but the result is the same, as you’d expect in a multiplication operation.

The repetition operator also has an augmented variation that you’ll call the augmented repetition operator. This variation uses the *= operator. Here’s how it works:

In the highlighted expression, the left-hand operand is the target list, while the right-hand operand is the integer value. You can’t swap them.

Again, the regular repetition operator returns a new list object containing the repeated data. However, its augmented variation mutates the target list in place, which makes it more efficient. As an exercise, go ahead and use the id() function to confirm this statement.

Reversing and Sorting Lists

Reversing and specially sorting lists of values are commonplace tasks in programming. In Python, you’ll have the built-in reversed() and sorted() functions to perform these tasks. When you’re working with lists, then you also have the .reverse() and .sort() methods, which reverse and sort the target list in place.

In the following sections, you’ll learn how to reverse and sort lists using the tools that Python provides for these tasks.

The built-in reversed() function takes a sequence as an argument and returns an iterator that yields the values of that sequence in reverse order:

When you call reversed() with a list as an argument, you get a reverse iterator object. This iterator yields values from the input list in reverse order. In this example, you use the list() constructor to consume the iterator and get the reversed data as a list.

Note: To dive deeper into reversing lists in Python, check out Reverse Python Lists: Beyond .reverse() and reversed() .

The reversed() function doesn’t modify the input object. You’ll typically use reversed() in loops as a way to iterate over your data in reverse order. If you need to keep a reference to your data, then you can use list() and assign its return value to a new variable, which will be completely independent of your original sequence.

It’s important to note that reversed() retrieves items from the input sequence lazily. This means that if something changes in the input sequence during the reversing process, then those changes will reflect in the final result:

In this example, you use the built-in next() function to consume the iterator value by value. The first call to next() returns the last item from numbers . Then you update the value of the second item from 2 to 222 . When you call next() again, you get 222 instead of 2 . This is because reversed() doesn’t create a copy of the input iterable. Instead, it works with a reference to it.

The reversed() function is great when you want to iterate over a list in reverse order without altering the original list. What if you have a list, and for some reason, you need to reverse its content persistently? In that case, you can use the .reverse() method:

The .reverse() method reverses a list in place. This means that if you call .reverse() on an existing list, then the changes will reflect in the underlying list.

Keep in mind that while reversed() returns an iterator, the .reverse() method returns None . This behavior may be the source of subtle errors when you’re starting to use lists. Consider the following code:

In this example, reversed_digits doesn’t get a list of reversed digits. Instead, it gets None because .reverse() mutates the underlying list in place and has no fruitful return value.

Finally, slicing is another technique that you can use to get a reversed copy of an existing list. To do this, you can use the following slicing operation:

The [::-1] variation of the slicing operator does the magic in this code. With this operator, you create a reversed copy of the original list. But how does it work? The third index, step , is typically a positive number, which is why a normal slicing operation extracts the items from left to right.

By setting step to a negative number, such as -1 , you tell the slicing operator to extract the items from right to left. That’s why you get a reversed copy of digits in the example above.

When you need to sort a list of values without altering the original list, you can use the built-in sorted() function. This function takes an iterable of values and returns a list of sorted values:

When you pass a list to sorted() , you get a list of sorted values as a result. The function doesn’t alter the original data in your list.

Note: It’s important to note that sorted() returns a list rather than an iterator. This behavior differs from reversed() , which returns an iterator instead of a list.

As you can see in the above example, Python sorts numbers according to their specific values. When it comes to sorting strings, things can be a bit confusing. Consider the following example:

What? The sorted list isn’t in alphabetical order. Why? Python sorts strings character by character using each character’s Unicode code point . Because uppercase letters come before lowercase letters in Python’s default character set , UTF-8 , you end up with "Hello" in the first position and "am" in the last.

Note: To dive deeper into sorting tools, check out How to Use sorted() and .sort() in Python .

You can use the built-in ord() function to get the Unicode code point of a character in Python:

As you can confirm in this code snippet, the uppercase "H" comes before the lowercase "a" in the Unicode table. That’s why you get "Hello" before "am" in the above example.

By default, the sorted() function sorts the items of a list in ascending order. If you need to sort the items in descending order, then you can use the reverse keyword-only argument . This argument defaults to False . If you set it to True , then you get the data in descending order:

By setting the reverse argument to True , you tell sorted() to sort the input iterable in reverse order. Isn’t that neat?

Note: As you already learned, lists can store objects of different types. However, heterogeneous lists often don’t allow you to sort their content:

In practice, you won’t find heterogeneous lists in many use cases. Sequences of heterogeneous objects are like a database record with a few known and immutable fields. In those cases, using a tuple is a better way to go.

To illustrate how sorted() can help you in the real world, say that you want to calculate the median of a numeric dataset or sample. The median is the value that lies in the middle when you sort the data. In most cases, your data won’t be sorted, so sorting will be the first step. Then you just need to locate the value in the middle.

If the number of values in your dataset is even, then the median is the average of the two values in the middle. Here’s a Python function that allows you to compute the median of a sample of values:

Inside median() , you use sorted() to sort the samples in ascending order. Then you check if your list has an odd number of data points, in which case, you return the item in the middle directly. If the list has an even number of samples, then you compute the index of the two items in the middle, calculate their average, and return the resulting value.

The sorted() function also accepts another keyword-only argument called key . This argument allows you to specify a one-argument function that will extract a comparison key from each list item.

As an example of how to use key , say that you have a list of tuples where each tuple holds an employee’s data, including the employee’s name, age, position, and salary. Now imagine that you want to sort the employees by their age.

In that situation, you can do something like the following:

In this example, you pass a lambda function to the key argument. This lambda takes an employee tuple as an argument and returns the age value, which lives at index 1 . Then sorted() uses this value to sort the tuples.

The key argument is quite useful in practice because it allows you to fine-tune the sorting process by changing the sorting criteria according to your specific needs.

If you need to sort a list in place instead of getting a new list of sorted data, then you can use the .sort() method. This method is similar to the sorted() function:

The main difference between sorted() and .sort() is that the former returns a new list of sorted data, while the latter sorts the target list in place. Also, because .sort() is a method, you need to call it on a list object.

Like most list methods that run mutations, .sort() returns None . For example, in the code below, you run into a common mistake that can occur when working with lists:

The .sort() method sorts the list in place and returns None to remind users that it operates by side effect . You must keep this behavior in mind because it can lead to subtle bugs.

You can also use the reverse and key keyword-only arguments with .sort() . They have the same meaning and functionality as the equivalent arguments in the sorted() function. Go ahead and give them a try!

Traversing Lists

When you’re working with lists in Python, one of the most common tasks that you’ll have to perform is to traverse the list while you run some transformation on the data or use the data for other purposes.

To traverse a list, you’ll need a loop that goes over each element from the start to the end of the list. Python provides several constructs that allow you to do this. The most popular are for loops and list comprehensions. You can also use some of Python’s functional programming tools for traversing lists.

In the following sections, you’ll learn how to traverse an existing list using these tools. To kick things off, you’ll start with for loops.

The recommended way to iterate over a list is to use a for loop. This kind of loop is quite readable and straightforward in Python. Here’s how it goes:

To use a for loop with a list, you place the list after the in keyword and provide a suitable loop variable. Don’t you think this loop is beautiful? Its meaning is clear, and it reads like plain English. That’s great!

Python’s for loops are intrinsically ready to operate on any iterable input directly. In this example, you’re using a list, but it’d work the same with a tuple, string, set, or any other iterable.

In the above example, the target iterable is your colors list. The loop variable, color , holds the current color in each iteration, and you can process each color in the loop’s body as needed. Note that if your list has a plural noun as its name, then the loop variable can use the same name in singular. This tiny detail will improve your loop’s readability.

A coding pattern that you’ll usually notice in code by people who come from other programming languages is that they tend to iterate over lists using a for loop that looks something like this:

This loop iterates over a range of integer numbers from 0 up to the length of the target list. In each iteration, you use the current index to access the associated item in the underlying list. Even though this loop works, it’s not Pythonic and is considered bad practice.

You don’t have to use a range of indices to iterate over a list in a for loop. Just go ahead and use your list directly in the loop definition. Python will take care of the rest.

Some people will argue that, in many situations, you’ll need to know the index of the current item to be able to perform some computations. That’s right! It’s especially true when you’re dealing with complex algorithms that operate on indices. In those cases, Python has you covered as well. It offers you the built-in enumerate() function, which you can use as in the following example:

The enumerate() function takes an iterable and returns an iterator. This iterator yields two-item tuples on demand. Each tuple will contain an index and the associated item.

Note: The enumerate() function also takes a second argument called start , which is optional and defaults to 0 . This argument allows you to specify a different starting point for the item count. However, if you set this argument to something different from 0 , then the resulting values won’t match the items’ indices in the underlying list.

To learn more about enumerate() , check out Python enumerate() : Simplify Loops That Need Counters .

Python provides many other tools that you can use when you’re iterating through a list of values. For example, you can use reversed() to iterate over the list in reverse order:

In this loop, you take advantage of the reversed() function to traverse your list of colors in reverse order, which might be a common requirement in your code.

Another common need is to traverse the list in sorted order. In this situation, you can use your old friend sorted() as in the code below:

The sorted() function allows you to get a new list of sorted data from your original list. Then you iterate over the new sorted list as usual.

If you continue digging into the Python tool kit, then you’ll find many other tools that will allow you to traverse your lists in different ways. For example, you’ll have the zip() function, which allows you to iterate over multiple lists in parallel:

In this example, you use zip() to iterate over three lists in parallel. The zip() function returns an iterator of tuples. The elements of each tuple come from the input iterables. In this example, the tuples combine items from integers , letters , and floats .

Note: For a deep dive into using the built-in zip() function, check out Using the Python zip() Function for Parallel Iteration .

Up to this point, all your list-traversing examples iterate over a list without performing any modification on the list itself. Modifying a list during iteration can lead to unexpected behavior and bugs, so avoid this practice. As a rule of thumb, if you need to modify the content of a list in a loop, then take a copy of that list first.

Say that you have a list of numbers, and you want to remove only odd values. In this situation, you can try something like this as your first attempt:

Unfortunately, only 9 and 1 were removed, while 5 remained in your list. This unexpected and incorrect behavior happened because removing items from a list shifts their indices, which interferes with the indices inside a running for loop. You can avoid this problem in a few ways.

For example, you can iterate over a copy of the original list:

This time, the result is correct. You use the [:] operator to create a shallow copy of your list. This copy allows you to iterate over the original data in a safe way. Once you have the copy, then you feed it into the for loop, as before.

Alternatively, you can iterate over the list in reverse order:

When you remove only the last item from the right end of a list on each iteration, you change the list length, but the indexing remains unaffected. This lets you correctly map indices to the corresponding list elements.

Note that this was just an illustrative example that relied on cherry-picked data. Remember that calling .remove() deletes the first occurrence of the given value, starting from the left side of the list, instead of the last one. If you had duplicate values on the list, then list elements would be removed in a different order.

While modifying list elements during iteration is less of a problem than deleting them, it also isn’t considered a good practice. It’s usually more desirable to create a completely new list and populate it with the transformed values:

This example shows a pretty common pattern in Python. The pattern consists of creating an empty list and then populating it in a loop. You’ll find this pattern in several Python codebases all around. It provides an intuitive and readable way to populate a list from scratch. However, you’ll often find that you can replace this pattern with something even better, a list comprehension.

List comprehensions are another great, popular way to traverse your lists. Comprehensions are fundamentally a list transformation tool. They allow you to create lists with transformed data out of another list or iterable.

To understand how comprehensions can help you transform your lists, refer to the example where you have a list of numbers as strings and want to turn them into integers. You can solve this problem with the following comprehension:

This comprehension iterates over the values in your original list. The expression in the comprehension runs the conversion from string to integer. The final result is a new list object, which you assign back to the numbers variable.

Note that this comprehension is equivalent to a loop with the enumerate() function:

The loop is more verbose and complicated because you need to call enumerate() and declare an extra indexing variable, i . On the other hand, the loop modifies the original list in place, while the list comprehension creates a new list.

You can also use comprehensions to filter existing lists. For example, say that you have a list of integer values and want to create a new list containing only the even values out of your original list:

The if clause in this list comprehension works as a filter that selects only the even numbers from your original data. How would you write a similar comprehension to retrieve the odd numbers?

You can also take advantage of some Python functional programming tools, such as map() and filter() , to traverse a list of values. These functions have an internal loop that iterates over the items of an input iterable and returns a given result.

For example, the map() function takes a transformation function and an iterable as arguments. Then it returns an iterator that yields items that result from applying the function to every item in the iterable.

Using map() , you can convert your list of numbers to integers with the following code:

In this example, map() applies int() to every item in numbers in a loop. Because map() returns an iterator, you’ve used the list() constructor to consume the iterator and show the result as a list.

Note: For a deeper dive into using the map() function, check out Python’s map() : Processing Iterables Without a Loop .

If you need to filter values from an existing list, then you can use the built-in filter() function. This function takes two arguments: a predicate function and an iterable of data. Then it returns an iterator that yields items that meet a given condition, which the predicate function tests for.

Here’s how filter() works in practice:

In this example, you use filter() to traverse your integers list and extract those values that satisfy the condition of being even numbers.

Note: For a deeper dive into using the filter() function, check out Python’s filter() : Extract Values From Iterables .

In Python, you’ll find a few other built-in and standard-library functions that allow you to traverse a list of values and obtain a final result either as another list, an iterator, or even a single value. Some examples include reduce() , min() and max() , sum() , all() , and any() . Note that some of these functions aren’t really functional programming tools, but they internally iterate over the input list.

Exploring Other Features of Lists

Python’s list has an impressive set of features, making it a versatile, flexible, and powerful data structure. So far, you’ve learned about most of these features. You’ve learned to create lists, add and remove items from your lists, traverse existing lists in a loop or comprehension, and much more.

In the following sections, you’ll learn about some additional features that make lists even more powerful. You’ll learn how to find items in a list, determine the minimum and maximum values, and get the list’s length. You’ll also explore the details of how Python compares lists to each other.

Python has a few tools that allow you to search for values in an existing list. For example, if you only need to quickly determine whether a value is present in a list, but you don’t need to grab the value, then you can use the in or not in operator, which will run a membership test on your list.

Note: To learn more about the in and not in operators and how to perform membership tests, check out Python’s “in” and “not in” Operators: Check for Membership . These operators can be pretty useful when you need to check if a Python string contains a substring .

As its name suggests, a membership test is a Boolean test that allows you to find out whether an object is a member of a collection of values. The general syntax for membership tests on list objects looks something like this:

The first expression allows you to determine if item is in list_object . It returns True if it finds item in list_object or False otherwise. The second expression works in the opposite way, allowing you to check if item is not in list_object . In this case, you get True if item doesn’t appear in list_object .

Here’s how membership tests work in practice:

In this example, you have a list of users and want to determine whether some specific users are registered in your system.

The first test uses in to check whether the user linda is registered. You get False because that user isn’t registered. The second test uses the not in operator, which returns True as a confirmation that linda isn’t one of your users.

The .index() method is another tool that you can use to find a given value in an existing list. This method traverses a list looking for a specified value. If the value is in the list, then the method returns its index. Otherwise, it raises a ValueError exception:

In the first call to .index() , you get the index where you can find the user named "eve" . You can use this index later in your code to access the actual object as needed. In the second call, because the user "linda" isn’t in the list, you get a ValueError with an explanatory message.

Note that if your search’s target value appears several times in the list, then .index() will return the index of the first occurrence:

The .index() method returns as soon as it finds the input value in the underlying list. So, if the value occurs many times, then .index() always returns the index of the first occurrence.

Lists provide yet another method that you can use for searching purposes. The method is called .count() , and it allows you to check how many times a given value is present in a list:

The .count() method takes an item as an argument and returns the number of times the input item appears in the underlying list. If the item isn’t in the list, then you get 0 .

Searching for a specific value in a Python list isn’t a cheap operation. The time complexity of .index() , .count() , and membership tests on lists is O(n) . Such linear complexity may be okay if you don’t need to perform many lookups. However, it can negatively impact performance if you need to run many of these operations.

While working with Python lists, you’ll face the need to obtain descriptive information about a given list. For example, you may want to know the number of items in the list, which is known as the list’s length . You may also want to determine the greatest and lowest values in the list. In all these cases, Python has you covered.

To determine the length of a list, you’ll use the built-in len() function. In the following example, you use this function as an intermediate step to calculate the average grade of a student:

Here, you calculate the average grade of a student. To do this, you use the sum() function to get the total sum and len() to get the number of evaluated subjects, which is the length of your grades list.

It’s important to note that because lists keep track of their length, calling len() is pretty fast, with a time complexity of O(1) . So, in most cases, you don’t need to store the return value of len() in an intermediate variable as you did in the example above.

Note: To learn more about using the len() function, check out Using the len() Function in Python .

Another frequent task that you’ll perform on lists, especially on lists of numeric values, is to find the minimum and maximum values. To do this, you can take advantage of the built-in min() and max() functions:

In these examples, you call min() and max() with a list of integer numbers . The call to min() returns the smallest number in the input list, -5 . In contrast, the call to max() returns the largest number in the list, or 9 .

Note: For a deeper dive into using the built-in min() and max() functions, check out Python’s min() and max() : Find Smallest and Largest Values .

Overall, Python lists support the len() , min() , and max() functions. With len() , you get the length of a list. With min() and max() , you get the smallest and largest values in a list. All these values can be fairly useful when you’re working with lists in your code.

You can also face the need to compare lists. Fortunately, list objects support the standard comparison operators . All these operators work by making item-by-item comparisons within the two involved lists:

When you compare two lists, Python uses lexicographical ordering . It compares the first two items from each list. If they’re different, this difference determines the comparison result. If they’re equal, then Python compares the next two items, and so on, until either list is exhausted.

In these examples, you compare lists of numbers using the standard comparison operators. In the first expression above, Python compares 2 and 2 , which are equal. Then it compares 3 and 3 to conclude that both lists are equal.

In the second expression, Python compares 5 and 5 . They’re equal, so Python has to compare 6 and 6 . They’re equal too, so the final result is False .

In the rest of the expressions, Python follows the same pattern to figure out the comparison. In short, Python compares lists in an item-by-item manner using lexicographical comparison. The first difference determines the result.

You can also compare lists of different lengths:

In the first expression, you get True as a result because 5 is less than 8 . That fact is sufficient for Python to solve the evaluation. In the second example, you get False . This result makes sense because the lists don’t have the same length, so they can’t be equal.

As you can see, comparing lists can be tricky. It’s also an expensive operation that, in the worst case, requires traversing two entire lists. Things get more complex and expensive when you compare lists of strings. In this situation, Python will also have to compare the strings character by character, which adds cost to the operation.

If you’re new to Python and are starting with lists, then you’ll want to be on the lookout for a few gotchas that can cause subtle issues in your code. Up to this point, you’ve learned what you need in order to understand most of these gotchas, so here’s a summary of the most common ones:

  • Confusing aliases of a list with copies : This can cause issues because changes to one alias affect others. Take a look at the Aliases of a List section for practical examples of this issue.
  • Forgetting that most list methods mutate the list in place and return None rather than a new list : This commonly leads to issues when you assign the return value of a list method to a variable, thinking that you have a new list, but you really get None . Check out the Reversing and Sorting Lists section for practical examples of this gotcha.
  • Confusing .append() with .extend() : This can cause issues because .append() adds a single item to the end of the list, while the .extend() method unpacks and adds multiple items. Have a look at the Growing and Shrinking Lists Dynamically section for details on how these methods work.
  • Using an empty list as a default argument value in function definitions : This can lead to unexpected behaviors because default argument values get defined when Python first parses the function.

You already know the explanation of the first three bullet points in this list. So, you only have to dive deeper into the last point. Why should you avoid using an empty list—or a list in general—as a default argument value? To answer this question, consider the following toy function:

This function appends item to the end of target , which defaults to an empty list. At first glance, it may seem that consecutive calls to append_to() will return single-item lists like in the following hypothetical example:

But because Python defines the default argument value when it first parses the function and doesn’t overwrite it in every call, you’ll be working with the same list object in every call. Therefore, you don’t get the above behavior. Instead, you get the following:

The target list remembers the data between calls. This happens because you’re using the same list object that appears as the default value in the function’s definition.

To prevent this issue, you can use None as the default value:

Great! You’ve solved the issue. Now your function returns single-item lists as expected. That’s because the function doesn’t retain the state between calls.

Sometimes you may need to create a list-like class that either extends the features of list or customizes some of its standard behaviors. For a long time, it was impossible to inherit directly from built-in Python types implemented in C . Python 2.2 fixed this issue. Now you can subclass built-in types , including list .

To understand how to do this, say that you’re working on an application to process and report your students’ grades. You want to create a list-like object to store the grades. Your custom list should have a method that computes the average grade. In this situation, you can create a list subclass like the following:

In this code snippet, you inherit from list directly. You can instantiate GradeList with an iterable of grade values. Note that the class works as a regular list. You can use list methods, such as .append() and .extend() , do indexing and slicing, and so on.

Additionally, you have a new .average() method in the class. This method isn’t part of the standard functionality of a list. So, this method extends list with new functionality.

The above example is a relatively safe way to subclass list because it doesn’t touch on any standard behavior. In contrast, things get a bit trickier when you need to customize the standard list behaviors.

For example, say that you want to continue improving your GradeList class, and you’re thinking of adding some input validation functionality. You want your class to validate any input grade to make sure it’s a number between 1 and 100 .

In this situation, you need to make considerable changes to the standard functionality of list . You’ll need to modify all the methods that add new items to your lists. These methods include the following special methods :

  • .__init__() , which initializes all the class’s new instances.
  • .__setitem__() , which supports indexing operations.

You’ll also have to customize the .append() , .extend() , and .insert() methods. Furthermore, if you want your class to validate the input when you run concatenations, then you’ll have to update other special methods, including .__add__() , .__radd__() , and .__iadd__() .

Here’s a possible, yet minimal, update of your GradeList class:

This class extends all the standard methods that add items to a regular list. All of these methods use the ._validate() helper method to guarantee that the input grades are valid. The method checks whether the values are numbers. It also checks if they’re between 0 and 100 .

As you can conclude from the above code, modifying the standard behavior of a list in a subclass requires a lot of work, and it’s highly prone to errors.

Here are a few examples of how the above class works in practice:

Great! Your GradeList class works as expected. It raises an exception whenever you try to introduce an invalid grade using any of the regular operations that add items to an existing list.

Note: For a deeper dive into creating list-like classes, check out Custom Python Lists: Inheriting From list vs UserList .

Subclassing the built-in list class can be both useful and challenging. While you can extend a list with relatively little effort, customizing its standard behavior comes with important challenges, as you learned in this section. So, before making the decision to subclass list , consider whether other techniques, such as composition , might be a better solution.

Putting Lists Into Action

So far, you’ve learned a ton about Python lists, their features, and their functionalities. You’ve dived into how to create new lists, make copies of existing lists, add items to your lists, traverse existing lists in a loop or a similar tool, create custom list-like classes, and much more.

Now you have all the required knowledge about lists to effectively solve common practical Python coding problems with them. In the following sections, you’ll code a few examples to help you solidify your new knowledge and understand how to use lists in real life.

Removing repeated items from an existing list is often a requirement in Python. You’ll probably manage to figure out several approaches to this problem. Using a set object could be one of them because sets don’t allow repeated items. So, you can do something like this:

This solution works because you get a new list of unique values. However, Python sets don’t necessarily keep the contained items in order. So, you may want to use another technique that preserves the original insertion order.

Arguably, the safest way to tackle the problem of removing repeated items from a list is to create a new list with unique values out of the original list. You can do this in a function like the following:

In this function, you accept a list as an argument. Then you define a new empty list to store the function’s result. In the loop, you iterate over the items in the input list. The conditional checks if the current item is absent in result . If that’s the case, then you add the item using .append() . Once the loop has finished, you return the resulting list, which will contain unique values.

Note that using the not in operator on larger lists can be too slow due to its linear time complexity. If that’s the case, then you may want to introduce an additional helper variable to hold copies of the unique values in a Python set :

You use the set to quickly determine if the given value is already present. Sets implement the in and not in operators differently, making them much faster than their list counterparts. While this functions returns instantaneously, it requires twice as much memory because you’re now storing every value in two places.

Creating a multidimensional list, such as a matrix or a list of lists, might also be a common requirement in your code. Again, you can tackle this problem in many different ways, depending on your specific needs.

A quick and safe way to create a multidimensional list is using a for loop or a comprehension. For example, say that you want to create a five-by-five matrix of numeric values, and you want to initialize all the values to 0 . You can do something like this:

In this example, you first create an empty list to store your matrix. Then you start a for loop that will run five times. In each iteration, you add a new empty list. So, your matrix will have five rows. Next, you start a nested loop that runs five times too.

Each time, you add a 0 to the current row using .append() . As a result, you get a five-by-five matrix with all its values initialized to 0 .

Note: In Python, you commonly use an underscore ( _ ) as a placeholder variable when the syntax requires a variable, but your code doesn’t.

You can get the same result as in the example above with a list comprehension like the following:

In this example, you use a list comprehension whose expression is another list comprehension. The inner comprehension provides the nested lists, while the outer comprehension builds the matrix.

You can make the above comprehension even more concise and readable by taking advantage of the repetition operator ( * ) as in the following code:

This new version of your list comprehension is way more readable than the previous one. It takes advantage of the repetition operator to build the rows of your matrix. This example might it seem like the following would work:

This output looks like what you need. It’s a list containing five nested lists. However, this resulting matrix internally works pretty differently from all the previous solutions. If you change one value in a given row, then the change will reflect in all the other rows:

In this example, you try to change the value of the first item in the first nested list or row. However, you actually changed the first value in all the rows. When you pass a list as an argument to the repetition operator, you get aliases of the list instead of copies. So, all the rows in your matrix are actually the same list.

Sometimes, you may need to process data that comes as a list of nested lists. Flattening this data into a one-dimensional list may be a common requirement in those scenarios. By flattening a list, you convert a multidimensional list, such as a matrix, into a one-dimensional list.

Note: To dive deeper into how to flatten a list of lists, check out How to Flatten a List of Lists in Python .

For example, suppose that you have the following list of lists:

Processing this list may be annoying because of its nested structure. So you need to flatten the list and get the following list instead:

How would you do this in Python? You’ll find several solutions to this problem for sure. In the code snippet below, you have one of them:

In the for loop above, you iterate over the nested lists in matrix . Then you use the .extend() method to add the current sublist’s contents to flattened_list as independent items. This loop produces a flattened list as a result.

Another useful list-related skill is to split an existing list into a certain number of chunks. This skill comes in handy when you need to distribute the workload across multiple threads or processes for concurrent processing.

Note: For a complete walk-through of splitting a list or iterable into chunks, check out How to Split a Python List or Iterable Into Chunks .

Again, you’ll find multiple solutions to this problem. The code below shows just one of them. Note that you won’t be using any standard-library or third-party specialized tool. You’ll code the solution based on your knowledge about lists:

In this function, you take the list to split and the number of items in every resulting chunk. Then you define a new empty list to store the chunks. The for loop iterates over a range of indices that goes from 0 to the length of your input list. Every iteration jumps through the desired chunk size.

To extract the chunks, you use a slicing operation. The loop variable, start , defines the start index, while the stop variable provides the stop index. Then you append every chunk to your chunks list, and that’s it.

You can use a Python list to emulate a stack or queue data structure. The .append() and .pop() methods will help you in that task. For example, to mimic a stack, or last-in-first-out (LIFO) data structure, you can use .append() to push an item onto the top of the stack. Similarly, you can use .pop() with no arguments to pop items from the top of the stack:

In this example, you represent a stack using a list. The stack will hold actions that you can undo. You start by creating an empty list called stack . Then you push hypothetical actions onto the stack using .append() , which adds the actions to the right end of the list.

The .pop() method returns the actions so that you can redo them. This method also removes the actions from the right end of the list following the LIFO order that distinguishes a stack data structure.

Note: For a deep dive into what stacks are and how to create them in Python, check out How to Implement a Python Stack .

Alternatively, if you want to emulate a queue, or a first-in-first-out (FIFO) data structure, then you can use .append() to place items at the end of the list, which is known as an enqueue operation. Similarly, you can use .pop() with 0 as an argument to return and remove items from the left end of the queue, which is known as a dequeue :

This list simulates a queue of people who may be arriving at a place to get some service. The .append() method allows you to add people to the end of the queue as they arrive. The .pop() method with 0 as an argument allows you to process people from the beginning of the queue when it’s their turn to access the service. Overall, you’re following the FIFO principle that rules queues.

Note: Check out Python Stacks, Queues, and Priority Queues in Practice for a complete walk-through of stacks and queues in Python.

By using a Python list, you can quickly take advantage of the standard list functionality to provide basic stack and queue operations, such as push, pop, enqueue, and dequeue. However, keep in mind that even though lists can help you simulate stacks and queues, they aren’t optimized for these use cases. Using a list as a queue is especially bad because it can make the queue terribly slow .

As you’ve learned throughout this tutorial, lists are powerful, flexible, versatile, and full-featured data structures. Because of their characteristics, people tend to use and abuse them. Yes, they’re suitable for many use cases, but sometimes they aren’t the best available option.

In general, you should use lists when you need to:

  • Keep your data ordered : Lists maintain the order of insertion of their items.
  • Store a sequence of values : Lists are a great choice when you need to store a sequence of related values.
  • Mutate your data : Lists are mutable data types that support multiple mutations.
  • Access random values by index : Lists allow quick and easy access to elements based on their index.

In contrast, avoid using lists when you need to:

  • Store immutable data : In this case, you should use a tuple. They’re immutable and more memory efficient.
  • Represent database records: In this case, consider using a tuple or a data class .
  • Store unique and unordered values : In this scenario, consider using a set or dictionary. Sets don’t allow duplicated values, and dictionaries can’t hold duplicated keys.
  • Run many membership tests where item doesn’t matter : In this case, consider using a set . Sets are optimized for this type of operation.
  • Run advanced array and matrix operations : In these situations, consider using NumPy’s specialized data structures.
  • Manipulate your data as a stack or queue : In those cases, consider using deque from the collections module or Queue , LifoQueue , or PriorityQueue . These data types are thread-safe and optimized for fast inserting and removing on both ends.

Depending on your specific scenario, lists may or may not be the right tool for the job. Therefore, you must carefully evaluate your needs and consider advanced data structures like the ones listed above.

Now you have a deep, solid understanding of the core features and functionalities of Python lists . Lists are everywhere. They’re an important part of the language itself and are present in the standard library , third-party packages, and in just about every piece of Python code that you’ll find out there. So, learning about them is a fundamental skill that you must have under your belt.

In this tutorial, you’ve learned how to:

  • Create new lists in Python using different approaches
  • Access one or more items in an existing list
  • Copy , update , grow , shrink , and concatenate existing Python lists
  • Sort , reverse , and traverse existing lists using built-in functions and methods
  • Use some other features of lists

With all this knowledge, you’re ready to write better, more effective code by taking advantage of Python lists. You’re also empowered to make informed decisions about when to use lists in your code.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Leodanis Pozo Ramos

Leodanis Pozo Ramos

Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Aldren Santos

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

What Do You Think?

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal . Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session . Happy Pythoning!

Keep Learning

Related Topics: intermediate data-structures python

Keep reading Real Python by creating a free account or signing in:

Already have an account? Sign-In

Almost there! Complete this form and click the button below to gain instant access:

Python's list Data Type: A Deep Dive With Examples (Sample Code)

🔒 No spam. We take your privacy seriously.

list representation meaning

  • Heap Data Structure
  • Implementing Heaps
  • B Trees (M-way Tree)
  • Introduction to Graphs
  • Graph Representations

Graph Representations - Adjacency Matrix and List

There are two ways in which we represent graphs, these are:

Adjacency Matrix

Adjacency list.

Both these have their advantages and disadvantages. In this tutorial, we will cover both of these graph representation along with how to implement them.

Adjacency matrix representation makes use of a matrix (table) where the first row and first column of the matrix denote the nodes (vertices) of the graph. The rest of the cells contains either 0 or 1 (can contain an associated weight w if it is a weighted graph).

Each row X column intersection points to a cell and the value of that cell will help us in determining that whether the vertex denoted by the row and the vertex denoted by the column are connected or not. If the value of the cell for v1 X v2 is equal to 1, then we can conclude that these two vertices v1 and v2 are connected by an edge, else they aren't connected at all.

Consider the given graph below:

Adjacency Matrix

The graph shown above is an undirected one and the adjacency matrix for the same looks as:

Matrix

The above matrix is the adjacency matrix representation of the graph shown above. If we look closely, we can see that the matrix is symmetric. Now let's see how the adjacency matrix changes for a directed graph.

Directed Graph

For the directed graph shown above the adjacency matrix will look something like this:

Directed Adjacency Matrix

Implementation of Adjacency Matrix

The structure ( constructor in Java ) for the adjacency matrix will look something like this:

It should also be noted that we have two class-level variables, like:

We have a constructor above named AdjacencyMatrix which takes the count of the number of the vertices that are present in the graph and then assigns our global vertex variable that value and also creates a 2D matrix of the same size. Now since our structure part is complete, we are simply left with adding the edges together, and the way we do that is:

In the above addEdge function we also assigned 1 for the direction from the destination to the start node, as in this code we looked at the example of the undirected graph, in which the relationship is a two-way process. If it had been a directed graph, then we can simply make this value equal to 0, and we would have a valid adjacency matrix.

Now the only thing left is to print the graph.

The entire code looks something like this:

The output of the above looks like:

Adjacency Matrix : 0 1 0 0 1 0 1 0 0 1 0 1 0 0 1 0

In the adjacency list representation, we have an array of linked-list where the size of the array is the number of the vertex (nodes) present in the graph. Each vertex has its own linked-list that contains the nodes that it is connected to.

Consider the graph shown below:

The above graph is an undirected one and the Adjacency list for it looks like:

Adjacency List

The first column contains all the vertices we have in the graph above and then each of these vertices contains a linked list that in turn contains the nodes that each vertex is connected to. For a directed graph the only change would be that the linked list will only contain the node on which the incident edge is present.

The above graph is a directed one and the Adjacency list for this looks like:

Adjacency List

Implementation of Adjacency List

The structure (constructor in Java) for the adjacency list will look something like this:

The above constructor takes the number of vertices as an argument and then assigns the class level variable this value, and then we create an array of LinkedList of the size of the vertices present in the graph. Finally, we create an empty LinkedList for each item of this array of LinkedList.

Now we have laid the foundations and the only thing left is to add the edges together, we do that like this:

We are taking the vertices from which an edge starts and ends, and we are simply inserting the destination vertex in the LinkedList of the start vertex and vice-versa (as it is for the undirected graph).

Node 0 is connected to: 1 Node 1 is connected to: 2 0 Node 2 is connected to: 3 1 Node 3 is connected to: 2

  • We learned how to represent the graphs in programming, via adjacency matrix and adjacency lists.
  • ← Introduction to Graphs ← PREV
  • Hash Table → NEXT →

C language

Study.com

In order to continue enjoying our site, we ask that you confirm your identity as a human. Thank you very much for your cooperation.

Python Tutorial

File handling, python modules, python numpy, python pandas, python matplotlib, python scipy, machine learning, python mysql, python mongodb, python reference, module reference, python how to, python examples, python lists.

Lists are used to store multiple items in a single variable.

Lists are one of 4 built-in data types in Python used to store collections of data, the other 3 are Tuple , Set , and Dictionary , all with different qualities and usage.

Lists are created using square brackets:

Create a List:

List items are ordered, changeable, and allow duplicate values.

List items are indexed, the first item has index [0] , the second item has index [1] etc.

When we say that lists are ordered, it means that the items have a defined order, and that order will not change.

If you add new items to a list, the new items will be placed at the end of the list.

Note: There are some list methods that will change the order, but in general: the order of the items will not change.

The list is changeable, meaning that we can change, add, and remove items in a list after it has been created.

Allow Duplicates

Since lists are indexed, lists can have items with the same value:

Lists allow duplicate values:

Advertisement

List Length

To determine how many items a list has, use the len() function:

Print the number of items in the list:

List Items - Data Types

List items can be of any data type:

String, int and boolean data types:

A list can contain different data types:

A list with strings, integers and boolean values:

From Python's perspective, lists are defined as objects with the data type 'list':

What is the data type of a list?

The list() Constructor

It is also possible to use the list() constructor when creating a new list.

Using the list() constructor to make a List:

Python Collections (Arrays)

There are four collection data types in the Python programming language:

  • List is a collection which is ordered and changeable. Allows duplicate members.
  • Tuple is a collection which is ordered and unchangeable. Allows duplicate members.
  • Set is a collection which is unordered, unchangeable*, and unindexed. No duplicate members.
  • Dictionary is a collection which is ordered** and changeable. No duplicate members.

*Set items are unchangeable, but you can remove and/or add items whenever you like.

**As of Python version 3.7, dictionaries are ordered . In Python 3.6 and earlier, dictionaries are unordered .

When choosing a collection type, it is useful to understand the properties of that type. Choosing the right type for a particular data set could mean retention of meaning, and, it could mean an increase in efficiency or security.

Get Certified

COLOR PICKER

colorpicker

Contact Sales

If you want to use W3Schools services as an educational institution, team or enterprise, send us an e-mail: [email protected]

Report Error

If you want to report an error, or if you want to make a suggestion, send us an e-mail: [email protected]

Top Tutorials

Top references, top examples, get certified.

Proportional Representation Foundation

Building a foundation for proportional representation in the united states.

  • About the PR Foundation
  • Membership Help
  • Benefits of PR
  • PR in a Nutshell
  • STV in a Nutshell
  • Answering Objections to PR
  • Semi-Proportional Methods
  • History of PR
  • Evolution of STV PR
  • STV PR in Practice
  • List PR in Practice
  • Applying PR: Case Studies
  • Online PR Resources
  • Bibliography
  • Reference Meek Rule
  • Reference WIGM Rule
  • Reference Andrae Rule
  • Trusted Elections
  • Risk-Limiting Audits

List Methods of Proportional Representation

With list PR, and voters vote for a list of candidates, rather than (or in addition to) voting for individual candidates. Each list, often a party list, is allocated its share of seats based on its proportion of the total vote, so that a list with 30% of the vote wins 30% of the seats, and so on. There is typically a threshold such that parties receiving less than some percentage of the vote (typically in the 1–5% range) win no seats at all. If a list wins enough votes to elect five seats, for example, the first five candidates on that list are elected.

Closed Lists

In closed-list systems, each list of candidates, along with its order, is determined by the party before the election. The voter votes for a list only, and not for individual candidates.

Closed-list PR is used in Norway, Israel, Iraq, South Africa and Peru, among other places.

In open-list systems, voters vote for one or more candidates on a list, and those votes determine (or at least influence) the order in which candidates are seated.

Open-list systems are used in Finland, Sweden, Brazil, and the Netherlands, among other places.

Mixed-Member PR Systems

In a mixed-member list system, one set of candidates is elected non-proportionally from single-member districts. These “constituency seats” are intended to guarantee geographically local representation. A second set of “list seats” is elected via a list-PR system, either open or closed. These seats are allocated to parties such that the overall list representation (including both constituency and list seats) is proportional.

(Mixed-member systems are often referred to as MMP, for mixed-member proportional, but we’ll avoid that abbreviation in favor of MMPR because of confusion with multi-member plurality systems, which are not proportional at all.)

Mixed-member PR is used in Germany, New Zealand, Bolivia and Italy, among other places.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

“Quote” 

Search , recent posts .

  • RCV: Introduction & Terminology
  • Droop & Python 3
  • Voting Referendum
  • Droop moves to GitHub!
  • Ernest Naville

Introduction to PR 

  • Proportional representation basics
  • Benefits of proportional representation
  • PR in a nutshell
  • Introduction to STV PR
  • Introduction to List PR
  • Entries feed
  • Comments feed
  • WordPress.org

WhiteHousePro by PageLines

Data Structures

Topics list.

  • Introduction to Algorithms
  • Performance Analysis
  • Space Complexity
  • Time Complexity
  • Asymptotic Notations
  • Linear & Non-Linear Data Structures
  • Single Linked List
  • Circular Linked List
  • Double Linked List
  • Sparse Matrix
  • Stack Using Array
  • Stack Using Linked List
  • Expressions
  • Infix to Postfix
  • Postfix Evaluation
  • Queue Using Array
  • Queue Using Linked List
  • Circular Queue
  • Double Ended Queue
  • Tree - Terminology

Tree Representations

  • Binary Tree
  • Binary Tree Representations
  • Binary Tree Traversals
  • Threaded Binary Trees
  • Max Priority Queue
  • Introduction to Graphs
  • Graph Representations
  • Graph Traversal - DFS
  • Graph Traversal - BFS
  • Linear Search
  • Binary Search
  • Insertion Sort
  • Selection Sort
  • Comparison of Sorting Methods
  • Binary Search Tree
  • Red - Black Trees
  • Splay Trees
  • Comparison of Search Trees
  • Knuth-Morris-Pratt Algorithm

A tree data structure can be represented in two methods. Those methods are as follows...

  • List Representation
  • Left Child - Right Sibling Representation

Consider the following tree...

1. List Representation

In this representation, we use two types of nodes one for representing the node with data called 'data node' and another for representing only references called 'reference node'. We start with a 'data node' from the root node in the tree. Then it is linked to an internal node through a 'reference node' which is further linked to any other node directly. This process repeats for all the nodes in the tree.

The above example tree can be represented using List representation as follows...

2. Left Child - Right Sibling Representation

In this representation, we use a list with one type of node which consists of three fields namely Data field, Left child reference field and Right sibling reference field. Data field stores the actual value of a node, left reference field stores the address of the left child and right reference field stores the address of the right sibling node. Graphical representation of that node is as follows...

In this representation, every node's data field stores the actual value of that node. If that node has left a child, then left reference field stores the address of that left child node otherwise stores NULL. If that node has the right sibling, then right reference field stores the address of right sibling node otherwise stores NULL. The above example tree can be represented using Left Child - Right Sibling representation as follows...

Website designed by Rajinikanth B

Javatpoint Logo

  • Data Structure
  • Design Pattern

DS Tutorial

Ds linked list, ds searching, differences.

JavaTpoint

In this article, we will discuss the ways to represent the graph. By Graph representation, we simply mean the technique to be used to store some graph into the computer's memory.

A graph is a data structure that consist a sets of vertices (called nodes) and edges. There are two ways to store Graphs into the computer's memory:

(or, Adjacency matrix representation) (or, Adjacency list representation)

In sequential representation, an adjacency matrix is used to store the graph. Whereas in linked list representation, there is a use of an adjacency list to store the graph.

In this tutorial, we will discuss each one of them in detail.

Now, let's start discussing the ways of representing a graph in the data structure.

In sequential representation, there is a use of an adjacency matrix to represent the mapping between vertices and edges of the graph. We can use an adjacency matrix to represent the undirected graph, directed graph, weighted directed graph, and weighted undirected graph.

If adj[i][j] = w, it means that there is an edge exists from vertex i to vertex j with weight w.

An entry A in the adjacency matrix representation of an undirected graph G will be 1 if an edge exists between V and V . If an Undirected Graph G consists of n vertices, then the adjacency matrix for that graph is n x n, and the matrix A = [aij] can be defined as -

a = 1 {if there is a path exists from V to V }

a = 0 {Otherwise}

It means that, in an adjacency matrix, 0 represents that there is no association exists between the nodes, whereas 1 represents the existence of a path between two edges.

If there is no self-loop present in the graph, it means that the diagonal entries of the adjacency matrix will be 0.

Now, let's see the adjacency matrix representation of an undirected graph.

There exist different adjacency matrices for the directed and undirected graph. In a directed graph, an entry A will be 1 only when there is an edge directed from V to V .

In a directed graph, edges represent a specific path from one vertex to another vertex. Suppose a path exists from vertex A to another vertex B; it means that node A is the initial node, while node B is the terminal node.

Consider the below-directed graph and try to construct the adjacency matrix of it.

It is similar to an adjacency matrix representation of a directed graph except that instead of using the '1' for the existence of a path, here we have to use the weight associated with the edge. The weights on the graph edges will be represented as the entries of the adjacency matrix. We can understand it with the help of an example. Consider the below graph and its adjacency matrix representation. In the representation, we can see that the weight associated with the edges is represented as the entries in the adjacency matrix.

Adjacency matrix is easier to implement and follow. An adjacency matrix can be used when the graph is dense and a number of edges are large.

Though, it is advantageous to use an adjacency matrix, but it consumes more space. Even if the graph is sparse, the matrix still consumes the same space.

An adjacency list is used in the linked representation to store the Graph in the computer's memory. It is efficient in terms of storage as we only have to store the values for edges.

Let's see the adjacency list representation of an undirected graph.

An adjacency list is maintained for each node present in the graph, which stores the node value and a pointer to the next adjacent node to the respective node. If all the adjacent nodes are traversed, then store the NULL in the pointer field of the last node of the list.

The sum of the lengths of adjacency lists is equal to twice the number of edges present in an undirected graph.

Now, consider the directed graph, and let's see the adjacency list representation of that graph.

Now, consider the weighted directed graph, and let's see the adjacency list representation of that graph.

In an adjacency list, it is easy to add a vertex. Because of using the linked list, it also saves space.

Now, let's see the implementation of adjacency matrix representation of graph in C.

In this program, there is an adjacency matrix representation of an undirected graph. It means that if there is an edge exists from vertex A to vertex B, there will also an edge exists from vertex B to vertex A.

Here, there are four vertices and five edges in the graph that are non-directed.

After the execution of the above code, the output will be -

Now, let's see the implementation of adjacency list representation of graph in C.

In this program, there is an adjacency list representation of an undirected graph. It means that if there is an edge exists from vertex A to vertex B, there will also an edge exists from vertex B to vertex A.

In the output, we will see the adjacency list representation of all the vertices of the graph. After the execution of the above code, the output will be -

Here, we have seen the description of graph representation using the adjacency matrix and adjacency list. We have also seen their implementations in C programming language.

So, that's all about the article. Hope, it will be helpful and informative to you.





Youtube

  • Send your Feedback to [email protected]

Help Others, Please Share

facebook

Learn Latest Tutorials

Splunk tutorial

Transact-SQL

Tumblr tutorial

Reinforcement Learning

R Programming tutorial

R Programming

RxJS tutorial

React Native

Python Design Patterns

Python Design Patterns

Python Pillow tutorial

Python Pillow

Python Turtle tutorial

Python Turtle

Keras tutorial

Preparation

Aptitude

Verbal Ability

Interview Questions

Interview Questions

Company Interview Questions

Company Questions

Trending Technologies

Artificial Intelligence

Artificial Intelligence

AWS Tutorial

Cloud Computing

Hadoop tutorial

Data Science

Angular 7 Tutorial

Machine Learning

DevOps Tutorial

B.Tech / MCA

DBMS tutorial

Data Structures

DAA tutorial

Operating System

Computer Network tutorial

Computer Network

Compiler Design tutorial

Compiler Design

Computer Organization and Architecture

Computer Organization

Discrete Mathematics Tutorial

Discrete Mathematics

Ethical Hacking

Ethical Hacking

Computer Graphics Tutorial

Computer Graphics

Software Engineering

Software Engineering

html tutorial

Web Technology

Cyber Security tutorial

Cyber Security

Automata Tutorial

C Programming

C++ tutorial

Control System

Data Mining Tutorial

Data Mining

Data Warehouse Tutorial

Data Warehouse

RSS Feed

Logo

  • Turnitin Guides
  • Administrator hub
  • Release notes and known issues
  • Welcome to Turnitin Guides

Welcome to Turnitin’s new website for guidance!

In 2024, we migrated our comprehensive library of guidance from https://help.turnitin.com to this site, guides.turnitin.com. During this process we have taken the opportunity to take a holistic look at our content and how we structure our guides.

This page is here to help you orientate yourself with these changes and update your resources

What's new?

We have restructured the content to help you navigate it more efficiently.

We are consolidating numerous pages to make our individual guides more valuable as well as removing duplicated content.

For example, our Similarity Report guidance on help.turnitin is repeated in numerous places to cater for each individual integration and license type. On guides.turnitin this content will exist in a single place to allow for users of all integrations and licenses to find it easily. We have made slight modifications to these guides to help you understand which guides are pertinent to you and your institution.

Our guidance search has greatly improved

As a result of our content restructure, the search functionality for guides.turnitin has improved. Use the search bar at the top of any page to locate the guidance you’re searching for.

Dedicated student and administrator guidance hubs

Visit the Student hub area to locate student guidance. For students who access Turnitin via an LMS or VLE, check out the subsection Submitting to Turnitin .

Visiting the Administrator hub area to locate administrator guidance and release notes. 

iThenticate and Crossref Similarity Check guidance is now located on a separate site

To improve the experience for our iThenticate and Crossref Similiarity Check customers we have move their help content onto a separate help site, guides.ithenticate.com . This will improve the search for all users.

We have also created an orientation page for this site to help users become acclimatised.

Some guidance is no longer grouped within the LMS umbrella

Some guidance which was previously provided under each LMS has been moved to sections that reflect those workflows’ outcomes. Use the table below as a cheatsheet to quickly locate guidance.

Student guidance
LMS guidance for administrators and instructors
Similarity Report and AI Writing guidance
Creating PeerMark assignments guidance
Creating and managing QuickMarks, rubrics and grading PeerMark assignments guidance
User profile guidance for administrators and instructors

Administrator account settings and migration help
Release notes and known issues

Articles in this section

  • Turnitin release notes
  • Integrations release notes
  • Integrations Known issues

Watch CBS News

See full RNC roll call of states vote results for the 2024 Republican nomination

By Melissa Quinn

Updated on: July 15, 2024 / 5:23 PM EDT / CBS News

Washington — Republican governors, lawmakers and nearly 2,500 delegates are convening in Milwaukee, Wisconsin , for the Republican National Convention , with former President Donald Trump formally receiving the party's 2024 nomination for president during a roll call vote of the state delegations Monday.

The roll call brings to an end the GOP presidential primary, though it's been known for months that Trump would be the party's choice to take on President Biden in November. The former president clinched the nomination in March, after he secured the 1,215 Republican delegates needed to become the presumptive GOP presidential nominee.

Trump announced Ohio Sen. J.D. Vance as his vice presidential running mate as the roll call was underway. Trump will also deliver a speech formally accepting the Republican presidential nomination to close out the convention Thursday.

With the announcement of Florida's 125 votes for Trump, delivered by his son, Eric Trump, the GOP officially nominated him for president. Eric Trump was accompanied by Donald Trump Jr., the former president's eldest son, and Tiffany Trump, his daughter.

House Speaker Mike Johnson, who is chair of the convention, announced at the conclusion of the roll call that 2,387 votes were cast for Trump.

"Let's make it official," he said. "Accordingly, the chair announces the President Donald J. Trump, having received a majority of the votes entitled to be cast at the convention, has been selected as the Republican Party nominee for president of the United States."

Results of the RNC roll call of states for 2024

State delegations announced their votes for the presidential nomination. Here is the breakdown of votes from each state and territory:

  • Iowa: 40 votes for Trump
  • Nevada: 26 votes for Trump
  • Oklahoma: 43 votes for Trump
  • West Virginia: 32 votes for Trump
  • New Hampshire: 22 votes for Trump
  • Nebraska: 36 votes for Trump
  • California: 169 votes for Trump
  • Tennessee: 58 votes for Trump
  • Washington state: 43 votes for Trump
  • Alabama: 50 votes for Trump
  • Massachusetts: 40 votes for Trump
  • Indiana: 58 votes for Trump
  • Georgia: 59 votes for Trump
  • Utah: 40 votes for Trump
  • Maryland: 37 votes for Trump
  • Texas: 161 votes for Trump
  • Ohio: 79 votes for Trump
  • American Samoa: 9 votes for Trump
  • Wisconsin: 41 votes for Trump
  • New York: 91 votes for Trump
  • Florida: 125 votes for Trump
  • Puerto Rico: 23 for Trump
  • Kentucky: 46 votes for Trump
  • Hawaii: 19 votes for Trump
  • Kansas: 39 votes for Trump
  • Louisiana: 47 votes for Trump
  • Delaware: 16 votes for Trump
  • Guam: 9 votes for Trump
  • Connecticut: 28 votes for Trump
  • Alaska: 29 votes for Trump
  • Oregon: 31 votes for Trump
  • Mississippi: 40 votes for Trump
  • Northern Mariana Islands: 9 votes for Trump
  • Wyoming: 29 votes for Trump
  • Maine: 20 votes for Trump
  • Missouri: 54 votes for Trump
  • Idaho: 32 votes for Trump
  • Illinois: 64 votes for Trump
  • North Dakota: 29 votes for Trump
  • Arizona: 43 votes for Trump
  • New Jersey: 12 votes for Trump
  • U.S. Virgin Islands: 4 votes for Trump
  • North Carolina: 62 votes for Trump; 12 votes to be cast pursuant to convention rules
  • Arkansas: 40 votes for Trump
  • Virginia: 42 votes for Trump; 6 votes to be cast pursuant to convention rules
  • Michigan: 51 votes for Trump; 4 votes to be cast pursuant to convention rules
  • Minnesota: 39 votes for Trump
  • Colorado: 37 votes for Trump
  • Rhode Island: 19 votes for Trump
  • Pennsylvania: 67 votes for Trump
  • South Dakota: 29 votes for Trump
  • New Mexico: 22 votes for Trump
  • Montana: 31 votes for Trump
  • South Carolina: 50 votes for Trump
  • Vermont: 17 votes for Trump
  • Washington, D.C.: 19 votes to be cast pursuant to convention rules

How does the RNC's roll call of states work?

During the roll call, the head of each state's and territory's delegation was called on to announce the votes of their state or territory's respective nomination for president. If a state delegation had passed when its name is called, it will be called again at the conclusion of the roll call.

Delegates are selected to represent their state or area at the convention, and most of those are bound to back Trump, as they're required to vote in accordance with the outcome of their state's primary or caucus. Roughly 150 delegates were unbound heading into the convention, since a small number of delegations, including those from Montana, New Mexico and South Dakota, were not required to vote for their state's chosen candidate.

Trump came into the convention with an estimated 2,243 delegates based on the results of primaries and caucuses held earlier this year, according to the CBS News Delegate Tracker.

What happens to delegates for candidates who have dropped out?

Though Trump cruised to victory during the primary elections, his former rival in the race, Nikki Haley, secured 94 delegates, according to the Delegate Tracker. Haley's campaign said she earned 97 delegates during the primary process.

But Haley announced last week she would be releasing those delegates and encouraged them to vote for Trump at the convention. State party rules dictate whether Haley's delegates are bound to her or whether they're free to vote for a different candidate since she withdrew from the presidential contest .

In Iowa, for example, Trump, Haley, Florida Gov. Ron DeSantis and biotech entrepreneur Vivek Ramaswaky secured delegates after the caucuses . But under state party rules, since Trump was the only candidate nominated at the convention, the entire 40-person delegation voted for him.

  • Republican National Convention

Melissa Quinn is a politics reporter for CBSNews.com. She has written for outlets including the Washington Examiner, Daily Signal and Alexandria Times. Melissa covers U.S. politics, with a focus on the Supreme Court and federal courts.

More from CBS News

Highlights from the 2024 Republican National Convention

JD Vance accepts GOP nomination and highlights Biden's age and his youth

Fact check of Trump, others on Day 4 of the Republican National Convention

2024 RNC Day 3 fact check of the Republican National Convention

list representation meaning

McKinsey technology trends outlook 2024

Despite challenging overall market conditions in 2023, continuing investments in frontier technologies promise substantial future growth in enterprise adoption. Generative AI (gen AI) has been a standout trend since 2022, with the extraordinary uptick in interest and investment in this technology unlocking innovative possibilities across interconnected trends such as robotics and immersive reality. While the macroeconomic environment with elevated interest rates has affected equity capital investment and hiring, underlying indicators—including optimism, innovation, and longer-term talent needs—reflect a positive long-term trajectory in the 15 technology trends we analyzed.

What’s new in this year’s analysis

This year, we reflected the shifts in the technology landscape with two changes on the list of trends: digital trust and cybersecurity (integrating what we had previously described as Web3 and trust architectures) and the future of robotics. Robotics technologies’ synergy with AI is paving the way for groundbreaking innovations and operational shifts across the economic and workforce landscapes. We also deployed a survey to measure adoption levels across trends.

These are among the findings in the latest McKinsey Technology Trends Outlook, in which the McKinsey Technology Council  identified the most significant technology trends unfolding today. This research is intended to help executives plan ahead by developing an understanding of potential use cases, sources of value, adoption drivers, and the critical skills needed to bring these opportunities to fruition.

Our analysis examines quantitative measures of interest, innovation, investment, and talent to gauge the momentum of each trend. Recognizing the long-term nature and interdependence of these trends, we also delve into the underlying technologies, uncertainties, and questions surrounding each trend. (For more about new developments in our research, please see the sidebar “What’s new in this year’s analysis”; for more about the research itself, please see the sidebar “Research methodology.”)

New and notable

The two trends that stood out in 2023 were gen AI and electrification and renewables. Gen AI has seen a spike of almost 700 percent in Google searches from 2022 to 2023, along with a notable jump in job postings and investments. The pace of technology innovation has been remarkable. Over the course of 2023 and 2024, the size of the prompts that large language models (LLMs) can process, known as “context windows,” spiked from 100,000 to two million tokens. This is roughly the difference between adding one research paper to a model prompt and adding about 20 novels to it. And the modalities that gen AI can process have continued to increase, from text summarization and image generation to advanced capabilities in video, images, audio, and text. This has catalyzed a surge in investments and innovation aimed at advancing more powerful and efficient computing systems. The large foundation models that power generative AI, such as LLMs, are being integrated into various enterprise software tools and are also being employed for diverse purposes such as powering customer-facing chatbots, generating ad campaigns, accelerating drug discovery, and more. We expect this expansion to continue, pushing the boundaries of AI capabilities. Senior leaders’ awareness of gen AI innovation has increased interest, investment, and innovation in AI technologies, such as robotics, which is a new addition to our trends analysis this year. Advancements in AI are ushering in a new era of more capable robots, spurring greater innovation and a wider range of deployments.

Research methodology

To assess the development of each technology trend, our team collected data on five tangible measures of activity: search engine queries, news publications, patents, research publications, and investment. For each measure, we used a defined set of data sources to find occurrences of keywords associated with each of the 15 trends, screened those occurrences for valid mentions of activity, and indexed the resulting numbers of mentions on a 0–1 scoring scale that is relative to the trends studied. The innovation score combines the patents and research scores; the interest score combines the news and search scores. (While we recognize that an interest score can be inflated by deliberate efforts to stimulate news and search activity, we believe that each score fairly reflects the extent of discussion and debate about a given trend.) Investment measures the flows of funding from the capital markets into companies linked with the trend.

Data sources for the scores include the following:

  • Patents. Data on patent filings are sourced from Google Patents, where the data highlight the number of granted patents.
  • Research. Data on research publications are sourced from Lens.
  • News. Data on news publications are sourced from Factiva.
  • Searches. Data on search engine queries are sourced from Google Trends.
  • Investment. Data on private-market and public-market capital raises (venture capital and corporate and strategic M&A, including joint ventures), private equity (including buyouts and private investment in public equity), and public investments (including IPOs) are sourced from PitchBook.
  • Talent demand. Number of job postings is sourced from McKinsey’s proprietary Organizational Data Platform, which stores licensed, de-identified data on professional profiles and job postings. Data are drawn primarily from English-speaking countries.

In addition, we updated the selection and definition of trends from last year’s report to reflect the evolution of technology trends:

  • The future of robotics trend was added since last year’s publication.
  • Data sources and keywords were updated. For data on the future of space technologies investments, we used research from McKinsey’s Aerospace & Defense Practice.

Finally, we used survey data to calculate the enterprise-wide adoption scores for each trend:

  • Survey scope. The survey included approximately 1,000 respondents from 50 countries.
  • Geographical coverage. Survey representation was balanced across Africa, Asia, Europe, Latin America, the Middle East, and North America.
  • Company size. Size categories, based on annual revenue, included small companies ($10 million to $50 million), medium-size companies ($50 million to $1 billion), and large companies (greater than $1 billion).
  • Respondent profile. The survey was targeted to senior-level professionals knowledgeable in technology, who reported their perception of the extent to which their organizations were using the technologies.
  • Survey method. The survey was conducted online to enhance reach and accessibility.
  • Question types. The survey employed multiple-choice and open-ended questions for comprehensive insights.
  • 1: Frontier innovation. This technology is still nascent, with few organizations investing in or applying it. It is largely untested and unproven in a business context.
  • 2: Experimentation. Organizations are testing the functionality and viability of the technology with a small-scale prototype, typically done without a strong focus on a near-term ROI. Few companies are scaling or have fully scaled the technology.
  • 3: Piloting. Organizations are implementing the technology for the first few business use cases. It may be used in pilot projects or limited deployments to test its feasibility and effectiveness.
  • 4: Scaling. Organizations are in the process of scaling the deployment and adoption of the technology across the enterprise. The technology is being scaled by a significant number of companies.
  • 5: Fully scaled. Organizations have fully deployed and integrated the technology across the enterprise. It has become the standard and is being used at a large scale as companies have recognized the value and benefits of the technology.

Electrification and renewables was the other trend that bucked the economic headwinds, posting the highest investment and interest scores among all the trends we evaluated. Job postings for this sector also showed a modest increase.

Although many trends faced declines in investment and hiring in 2023, the long-term outlook remains positive. This optimism is supported by the continued longer-term growth in job postings for the analyzed trends (up 8 percent from 2021 to 2023) and enterprises’ continued innovation and heightened interest in harnessing these technologies, particularly for future growth.

Photo of McKinsey Partners, Lareina Yee and Roger Roberts

Future frontiers: Navigating the next wave of tech innovations

Join Lareina Yee and Roger Roberts on Tuesday, July 30, at 12:30 p.m. EDT/6:30 p.m. CET as they discuss the future of these technological trends, the factors that will fuel their growth, and strategies for investing in them through 2024 and beyond.

In 2023, technology equity investments fell by 30 to 40 percent to approximately $570 billion due to rising financing costs and a cautious near-term growth outlook, prompting investors to favor technologies with strong revenue and margin potential. This approach aligns with the strategic perspective leading companies are adopting, in which they recognize that fully adopting and scaling cutting-edge technologies is a long-term endeavor. This recognition is evident when companies diversify their investments across a portfolio of several technologies, selectively intensifying their focus on areas most likely to push technological boundaries forward. While many technologies have maintained cautious investment profiles over the past year, gen AI saw a sevenfold increase in investments, driven by substantial advancements in text, image, and video generation.

About QuantumBlack, AI by McKinsey

QuantumBlack, McKinsey’s AI arm, helps companies transform using the power of technology, technical expertise, and industry experts. With thousands of practitioners at QuantumBlack (data engineers, data scientists, product managers, designers, and software engineers) and McKinsey (industry and domain experts), we are working to solve the world’s most important AI challenges. QuantumBlack Labs is our center of technology development and client innovation, which has been driving cutting-edge advancements and developments in AI through locations across the globe.

Despite an overall downturn in private equity investment, the pace of innovation has not slowed. Innovation has accelerated in the three trends that are part of the “AI revolution” group: gen AI, applied AI, and industrializing machine learning. Gen AI creates new content from unstructured data (such as text and images), applied AI leverages machine learning models for analytical and predictive tasks, and industrializing machine learning accelerates and derisks the development of machine learning solutions. Applied AI and industrializing machine learning, boosted by the widening interest in gen AI, have seen the most significant uptick in innovation, reflected in the surge in publications and patents from 2022 to 2023. Meanwhile, electrification and renewable-energy technologies continue to capture high interest, reflected in news mentions and web searches. Their popularity is fueled by a surge in global renewable capacity, their crucial roles in global decarbonization efforts, and heightened energy security needs amid geopolitical tensions and energy crises.

The talent environment largely echoed the investment picture in tech trends in 2023. The technology sector faced significant layoffs, particularly among large technology companies, with job postings related to the tech trends we studied declining by 26 percent—a steeper drop than the 17 percent decrease in global job postings overall. The greater decline in demand for tech-trends-related talent may have been fueled by technology companies’ cost reduction efforts amid decreasing revenue growth projections. Despite this reduction, the trends with robust investment and innovation, such as gen AI, not only maintained but also increased their job postings, reflecting a strong demand for new and advanced skills. Electrification and renewables was the other trend that saw positive job growth, partially due to public sector support for infrastructure spending.

Even with the short-term vicissitudes in talent demand, our analysis of 4.3 million job postings across our 15 tech trends underscored a wide skills gap. Compared with the global average, fewer than half the number of potential candidates have the high-demand tech skills specified in job postings. Despite the year-on-year decreases for job postings in many trends from 2022 to 2023, the number of tech-related job postings in 2023 still represented an 8 percent increase from 2021, suggesting the potential for longer-term growth (Exhibit 1).

Enterprise technology adoption momentum

The trajectory of enterprise technology adoption is often described as an S-curve that traces the following pattern: technical innovation and exploration, experimenting with the technology, initial pilots in the business, scaling the impact throughout the business, and eventual fully scaled adoption (Exhibit 2). This pattern is evident in this year’s survey analysis of enterprise adoption conducted across our 15 technologies. Adoption levels vary across different industries and company sizes, as does the perceived progress toward adoption.

Technologies progress through different stages, with some at the leading edge of innovation and others approaching large-scale adoption.

Image description:

A graph depicts the adoption curve of technology trends, scored from 1 to 5, where 1 represents frontier innovation, located at the bottom left corner of the curve; 2 is experimenting, located slightly above frontier innovation; 3 is piloting, which follows the upward trajectory of the curve; 4 is scaling, marked by a vertical ascent as adoption increases; and 5 is fully scaled, positioned at the top of the curve, indicating near-complete adoption.

In 2023, the trends are positioned along the adoption curve as follows: future of space technologies and quantum technologies are at the frontier innovation stage; climate technologies beyond electrification and renewables, future of bioengineering, future of mobility, future of robotics, and immersive-reality technologies are at the experimenting stage; digital trust and cybersecurity, electrification and renewables, industrializing machine learning, and next-gen software development are at the piloting stage; and advanced connectivity, applied AI, cloud and edge computing, and generative AI are at the scaling stage.

Footnote: Trend is more relevant to certain industries, resulting in lower overall adoption across industries compared with adoption within relevant industries.

Source: McKinsey technology adoption survey data

End of image description.

We see that the technologies in the S-curve’s early stages of innovation and experimenting are either on the leading edge of progress, such as quantum technologies and robotics, or are more relevant to a specific set of industries, such as bioengineering and space. Factors that could affect the adoption of these technologies include high costs, specialized applications, and balancing the breadth of technology investments against focusing on a select few that may offer substantial first-mover advantages.

As technologies gain traction and move beyond experimenting, adoption rates start accelerating, and companies invest more in piloting and scaling. We see this shift in a number of trends, such as next-generation software development and electrification. Gen AI’s rapid advancement leads among trends analyzed, about a quarter of respondents self-reporting that they are scaling its use. More mature technologies, like cloud and edge computing and advanced connectivity, continued their rapid pace of adoption, serving as enablers for the adoption of other emerging technologies as well (Exhibit 3).

More-mature technologies are more widely adopted, often serving as enablers for more-nascent technologies.

A segmented bar graph shows the adoption levels of tech trends in 2023 as a percentage of respondents. The trends are divided into 5 segments, comprising 100%: fully scaled, scaling, piloting, experimenting, and not investing. The trends are arranged based on the combined percentage sum of fully scaled and scaling shares. Listed from highest to lowest, these combined percentages are as follows:

  • cloud and edge computing at 48%
  • advanced connectivity at 37%
  • generative AI at 36%
  • applied AI at 35%
  • next-generation software development at 31%
  • digital trust and cybersecurity at 30%
  • electrification and renewables at 28%
  • industrializing machine learning at 27%
  • future of mobility at 21%
  • climate technologies beyond electrification and renewables at 20%
  • immersive-reality technologies at 19%
  • future of bioengineering at 18%
  • future of robotics at 18%
  • quantum technologies at 15%
  • future of space technologies at 15%

The process of scaling technology adoption also requires a conducive external ecosystem where user trust and readiness, business model economics, regulatory environments, and talent availability play crucial roles. Since these ecosystem factors vary by geography and industry, we see different adoption scenarios playing out. For instance, while the leading banks in Latin America are on par with their North American counterparts in deploying gen AI use cases, the adoption of robotics in manufacturing sectors varies significantly due to differing labor costs affecting the business case for automation.

As executives navigate these complexities, they should align their long-term technology adoption strategies with both their internal capacities and the external ecosystem conditions to ensure the successful integration of new technologies into their business models. Executives should monitor ecosystem conditions that can affect their prioritized use cases to make decisions about the appropriate investment levels while navigating uncertainties and budgetary constraints on the way to full adoption (see the “Adoption developments across the globe” sections within each trend or particular use cases therein that executives should monitor). Across the board, leaders who take a long-term view—building up their talent, testing and learning where impact can be found, and reimagining the businesses for the future—can potentially break out ahead of the pack.

Lareina Yee is a senior partner in McKinsey’s Bay Area office, where Michael Chui  is a McKinsey Global Institute partner, Roger Roberts  is a partner, and Mena Issler is an associate partner.

The authors wish to thank the following McKinsey colleagues for their contributions to this research: Aakanksha Srinivasan, Ahsan Saeed, Alex Arutyunyants, Alex Singla, Alex Zhang, Alizee Acket-Goemaere, An Yan, Anass Bensrhir, Andrea Del Miglio, Andreas Breiter, Ani Kelkar, Anna Massey, Anna Orthofer, Arjit Mehta, Arjita Bhan, Asaf Somekh, Begum Ortaoglu, Benjamin Braverman, Bharat Bahl, Bharath Aiyer, Bhargs Srivathsan, Brian Constantine, Brooke Stokes, Bryan Richardson, Carlo Giovine, Celine Crenshaw, Daniel Herde, Daniel Wallance, David Harvey, Delphine Zurkiya, Diego Hernandez Diaz, Douglas Merrill, Elisa Becker-Foss, Emma Parry, Eric Hazan, Erika Stanzl, Everett Santana, Giacomo Gatto, Grace W Chen, Hamza Khan, Harshit Jain, Helen Wu, Henning Soller, Ian de Bode, Jackson Pentz, Jeffrey Caso, Jesse Klempner, Jim Boehm, Joshua Katz, Julia Perry, Julian Sevillano, Justin Greis, Kersten Heineke, Kitti Lakner, Kristen Jennings, Liz Grennan, Luke Thomas, Maria Pogosyan, Mark Patel, Martin Harrysson, Martin Wrulich, Martina Gschwendtner, Massimo Mazza, Matej Macak, Matt Higginson, Matt Linderman, Matteo Cutrera, Mellen Masea, Michiel Nivard, Mike Westover, Musa Bilal, Nicolas Bellemans, Noah Furlonge-Walker, Obi Ezekoye, Paolo Spranzi, Pepe Cafferata, Robin Riedel, Ryan Brukardt, Samuel Musmanno, Santiago Comella-Dorda, Sebastian Mayer, Shakeel Kalidas, Sharmila Bhide, Stephen Xu, Tanmay Bhatnagar, Thomas Hundertmark, Tinan Goli, Tom Brennan, Tom Levin-Reid, Tony Hansen, Vinayak HV, Yaron Haviv, Yvonne Ferrier, and Zina Cole.

They also wish to thank the external members of the McKinsey Technology Council for their insights and perspectives, including Ajay Agrawal, Azeem Azhar, Ben Lorica, Benedict Evans, John Martinis, and Jordan Jacobs.

Special thanks to McKinsey Global Publishing colleagues Barr Seitz, Diane Rice, Kanika Punwani, Katie Shearer, LaShon Malone, Mary Gayen, Nayomi Chibana, Richard Johnson, Stephen Landau, and Victor Cuevas for making this interactive come alive.

Explore a career with us

Related articles.

Blue, green, red, brown and white wire in wave pattern on dark blue background - stock photo

Rewired and running ahead: Digital and AI leaders are leaving the rest behind

Close-up eye and a futuristic data screen panel on a dark blue background.

False friends or good ends? The CIO’s four-point guide to navigating technology trends

IMAGES

  1. PPT

    list representation meaning

  2. PPT

    list representation meaning

  3. Linked List

    list representation meaning

  4. PPT

    list representation meaning

  5. Data Structure and Algorithms

    list representation meaning

  6. PPT

    list representation meaning

VIDEO

  1. Data Structures L38: Overview of Graph, adjacency Matrix and Adj List, search Operations (BFS,DFS)

  2. Representation

  3. Representation Learning: Basic and Key Features

  4. Legal representation

  5. Pictorial Representation Meaning In Hindi, Pictorial Representation

  6. Queue using linked list animation

COMMENTS

  1. Adjacency list

    Adjacency list. This undirected cyclic graph can be described by the three unordered lists {b, c }, {a, c }, {a, b }. In graph theory and computer science, an adjacency list is a collection of unordered lists used to represent a finite graph. Each unordered list within an adjacency list describes the set of neighbors of a particular vertex in ...

  2. Linked List Data Structure

    A linked list is a linear data structure, in which the elements are not stored at contiguous memory locations. The elements in a linked list are linked using pointers as shown in the below image: Applications of linked list in computer science:Implementation of stacks and queuesImplementation of graphs: Adjacency list representation of graphs is th

  3. Adjacency List (With Code in C, C++, Java and Python)

    An adjacency list represents a graph as an array of linked lists. The index of the array represents a vertex and each element in its linked list represents the other vertices that form an edge with the vertex. For example, we have a graph below. We can represent this graph in the form of a linked list on a computer as shown below. Here, 0, 1, 2 ...

  4. Adjacency List meaning & definition in DSA

    An adjacency list is a data structure used to represent a graph where each node in the graph stores a list of its neighboring vertices. Characteristics of the Adjacency List: The size of the matrix is determined by the number of nodes in the network. The number of graph edges is easily computed. The adjacency list is a jagged array.

  5. Representing graphs (article)

    The adjacency list representation for an undirected graph is just an adjacency list for a directed graph, where every undirected edge connecting A to B is represented as two directed edges: -one from A->B -one from B->A e.g. if you have a graph with undirected edges connecting 0 to 1 and 1 to 2 your adjacency list would be: [ [1] //edge 0->1

  6. Graph Representation: Adjacency List and Matrix

    To store the adjacency list, we need O(V + E) O ( V + E) space as we need to store every vertex and their neighbors (edges). To find if a vertex has a neighbor, we need to go through the linked list of the vertex. This requires O(1 + deg(V)) O ( 1 + d e g ( V)) time. If we use balanced binary search trees, it becomes O(1 + log(deg(V)) O ( 1 ...

  7. 8.4: Graph Representations

    Adjacency Matrix Representation. An adjacency matrix is one of the two common ways to represent a graph. The adjacency matrix shows which nodes are adjacent to one another. Two nodes are adjacent if there is an edge connecting them. In the case of a directed graph, if node j j is adjacent to node i i, there is an edge from i i to j j.

  8. Building and Analyzing Graphs with the Adjacency List

    This lesson provides a deep insight into the Adjacency List representation of graphs. It explains the basics of the Adjacency List, including clear and helpful visual examples. The lesson guides students through the step-by-step process of building their own graph in Python, using the Adjacency List representation. It then dives into the analysis of the time and space complexity of operations ...

  9. Adjacency List Representation of Graph

    Adjacency List. In Adjacency List, we use an array of a list to represent the graph. The list size is equal to the number of vertex (n). Let's assume the list of size n as Adjlist [n] Adjlist [0] will have all the nodes which are connected to vertex 0. Adjlist [1] will have all the nodes which are connected to vertex 1 and so on.

  10. Adjacency List -- from Wolfram MathWorld

    The adjacency list representation of a graph consists of n lists one for each vertex v_i, 1<=i<=n, which gives the vertices to which v_i is adjacent. The adjacency lists of a graph g may be computed in the Wolfram Language using AdjacencyList[g, #]& /@ VertexList[g]and a graph may be constructed from adjacency lists l using Graph[UndirectedEdge @@@ Union[ Sort /@ Flatten[ MapIndexed[{#, #2[[1 ...

  11. Party-list proportional representation

    e. Poster for the European Parliament election 2004 in Italy, showing party lists. Party-list proportional representation ( list-PR) is a system of proportional representation based on preregistered political parties, with each party being allocated a certain number of seats roughly proportional to their share of the vote. [1] In these systems ...

  12. Represent a Graph Using a Linked List

    By default, it is directed. Insert an edge: It takes a pair (v1, v2) and adds it to the graph. If it is a directed graph, we only add v2 in the linked list of vertex v1 located at index v1. Otherwise, we also insert an edge from v2 to v1. Display the graph: It shows the complete graph represented using linked lists. The code is given below.

  13. Graphs and graph representations

    Adjacency list representation. Since sparse graphs are common, the adjacency list representation is often preferred. This representation keeps track of the outgoing edges from each vertex, typically as a linked list. For example, the graph above might be represented with the following data structure:

  14. Representing Graphs in Python (Adjacency List and Matrix)

    An adjacency list is a hash map that maps each node to a list of neighbors. This combines the benefits of both the edge list and the adjacency matrix, by providing a contained structure that allows you to easily see a node's neighbors. In Python, adjacency lists are often represented as dictionaries.

  15. Graph and its representations

    Representation of Directed Graph to Adjacency list: The below directed graph has 3 vertices. So, an array of list will be created of size 3, where each indices represent the vertices. Now, vertex 0 has no neighbours. For vertex 1, it has two neighbour (i.e, 0 and 2) So, insert vertices 0 and 2 at indices 1 of array.

  16. Python's list Data Type: A Deep Dive With Examples

    Python's list is a flexible, versatile, powerful, and popular built-in data type. It allows you to create variable-length and mutable sequences of objects. In a list, you can store objects of any type. You can also mix objects of different types within the same list, although list elements often share the same type.

  17. Graph Representations

    Adjacency Matrix. Adjacency matrix representation makes use of a matrix (table) where the first row and first column of the matrix denote the nodes (vertices) of the graph. The rest of the cells contains either 0 or 1 (can contain an associated weight w if it is a weighted graph). Each row X column intersection points to a cell and the value of ...

  18. Graphs and graph representations

    Adjacency list representation. Since sparse graphs are quite common, the adjacency list representation is often preferred. This representation keeps track of the outgoing edges from each vertex, typically as a linked list. For example, the graph above might be represented with the following data structure:

  19. Adjacency Matrix & List

    Adjacency lists are used to represent graphs in discrete mathematics. These lists condense a visual representation into lines of text that can be represented as vertices connected by simple arrows ...

  20. Python Lists

    List. Lists are used to store multiple items in a single variable. Lists are one of 4 built-in data types in Python used to store collections of data, the other 3 are Tuple, Set, and Dictionary, all with different qualities and usage. Lists are created using square brackets:

  21. List PR

    With list PR, and voters vote for a list of candidates, rather than (or in addition to) voting for individual candidates. Each list, often a party list, is allocated its share of seats based on its proportion of the total vote, so that a list with 30% of the vote wins 30% of the seats, and so on. There is typically a threshold such that parties ...

  22. Data Structures Tutorials

    List Representation. In this representation, we use two types of nodes one for representing the node with data called 'data node' and another for representing only references called 'reference node'. We start with a 'data node' from the root node in the tree. Then it is linked to an internal node through a 'reference node' which is further ...

  23. Graph Representation

    A graph is a data structure that consist a sets of vertices (called nodes) and edges. There are two ways to store Graphs into the computer's memory: Sequential representation (or, Adjacency matrix representation) Linked list representation (or, Adjacency list representation) In sequential representation, an adjacency matrix is used to store the ...

  24. Welcome to Turnitin Guides

    Welcome to Turnitin's new website for guidance! In 2024, we migrated our comprehensive library of guidance from https://help.turnitin.com to this site, guides.turnitin.com. During this process we have taken the opportunity to take a holistic look at our content and how we structure our guides.

  25. See full RNC roll call of states vote results for the 2024 Republican

    With the announcement of Florida's 125 votes for Trump, delivered by his son, Eric Trump, the GOP officially nominated him for president. Eric Trump was accompanied by Donald Trump Jr., the former ...

  26. McKinsey technology trends outlook 2024

    Survey representation was balanced across Africa, Asia, Europe, Latin America, the Middle East, and North America. ... Definition of enterprise-wide adoption scores: 1: Frontier innovation. This technology is still nascent, with few organizations investing in or applying it. It is largely untested and unproven in a business context.