I want to iterate over positions (x, y) in a 2-dimensional grid and call a function for each position (providing x and y as parameters). I know how this can be done using for loops, but I'd like to write this using functional programming methods such that I can later take advantage of libraries like Rayon. I've managed to build a functional version, but it seems complicated and I wanted to ask whether there's a cleaner way to achieve this.
Here's a small example of computing the maximum sum of all 2x2 patches in a grid:
use std::cmp::max;
const WIDTH: usize = 4;
const HEIGHT: usize = 3;
type Grid = [[u32; WIDTH]; HEIGHT];
fn main() {
let grid: Grid = [
[1, 3, 5, 8],
[3, 9, 4, 2],
[3, 4, 5, 0],
];
let coords = (0..WIDTH - 1).flat_map(|x| (0..HEIGHT - 1).map(move |y| (x, y)));
let max_sum = coords.map(|(x, y)| sum_2x2(x, y, &grid)).max().unwrap();
println!("Max 2x2 patch: {}", max_sum);
}
fn sum_2x2(x: usize, y: usize, grid: &Grid) -> u32 {
[
grid[y][x],
grid[y][x + 1],
grid[y + 1][x],
grid[y + 1][x + 1],
]
.iter()
.sum()
}
The line let coords = (0..WIDTH - 1).flat_map(|x| (0..HEIGHT - 1).map(move |y| (x, y))); seems pretty complex for such a simple task. In Python, I'd do the following to get the positions (which I think is much cleaner):
>>> from itertools import product
>>> product(range(HEIGHT-1), range(WIDTH-1))
Is there a better way to write this or do I just need to get used to it?
You can possibly simplify the code with one of the loop comprehension crates like map_for or mdo.
For example with map_for:
let max_sum = map_for!(
move;
x <- 0..WIDTH-1;
y <- 0..HEIGHT-1;
=> sum_2x2(x, y, &grid)
).max().unwrap();
I haven't tried it with Rayon, but since map_for is simply syntax sugar for a sequence of calls to map and flat_map and works with any type that implements those functions, it should work with Rayon as well.
Full disclosure: I am the author of the map_for crate.
Another option is to use cartesian_product from the Itertools crate, which has many convenient methods involving iterators.
use itertools::Itertools;
(0..WIDTH-1).cartesian_product(0..HEIGHT-1).map(|(x, y)| sum_2x2(x, y, &grid)).max()
As #hellow pointed out in the comments, there's an iproduct macro in the itertools crate that does what I want. Using it, my code can be rewritten like this:
use itertools::iproduct;
...
let coords = iproduct!(0..WIDTH - 1, 0..HEIGHT - 1);
...
That's exactly what I was looking for. Thanks to everyone who commented / posted answers here.
PS: If you want to use an itertools iterator with rayon, you can do this by using the par_bridge method, e.g. iproduct!(0..WIDTH - 1, 0..HEIGHT - 1).par_iter();. Took me a while to figure that out since par_iter and into_par_iter don't work.
Related
I'm a beginner in functional programming but I'm famaliar with imperative programming. I'm having trouble translating a piece of cpp code involving updatating two objects at the same time (context is n-body simulation).
It's roughly like this in c++:
for (Particle &i: particles) {
for (Particle &j: particles) {
collide(i, j) // function that mutates particles i and j
}
}
I'm translating this to Ocaml, with immutable objects and immutable Lists. The difficult part is that I need to replace two objects at the same time. So far I have this:
List.map (fun i ->
List.map (fun j ->
let (new_i, new_j) = collide(i, j) in // function that returns new particles i, j
// how do i update particles with new i, j?
) particles
) particles
How do I replace both objects in the List at the same time?
The functional equivalent of the imperative code is just as simple as,
let nbody f xs =
List.map (fun x -> List.fold_left f x xs) xs
It is a bit more generic, as a I abstracted the collide function and made it a parameter. The function f takes two bodies and returns the state of the first body as affected by the second body. For example, we can implement the following symbolic collide function,
let symbolic x y = "f(" ^ x ^ "," ^ y ^ ")"
so that we can see the result and associativity of the the collide function application,
# nbody symbolic [
"x"; "y"; "z"
];;
- : string list =
["f(f(f(x,x),y),z)"; "f(f(f(y,x),y),z)"; "f(f(f(z,x),y),z)"]
So, the first element of the output list is the result of collision of x with x itself, then with y, then with z. The second element is the result of collision of y with x, and y, and z. And so on.
Obviously the body shall not collide with itself, but this could be easily fixed by either modifying the collide function or by filtering the input list to List.fold and removing the currently being computed element. This is left as an exercise.
List.map returns a new list. The function you supply to List.map may transform the elements from one type to another or just apply some operation on the same type.
For example, let's assume you start with a list of integer tuples
let int_tuples = [(1, 3); (4, 3); (8, 2)];;
and let's assume that your update function takes an integer tuple and doubles the integers:
let update (i, j) = (i * 2, j * 2) (* update maybe your collide function *)
If you now do:
let new_int_tuples = List.map update int_tuples
You'll get
(* [(2, 6); (8, 6); (16, 4)] *)
Hope this helps
I think I ended up figuring out a solution while asking this but figured I'd leave the question up anyway...
I'm working with n-dimensional matrices using the ndarray crate and I need to take dot-products of multiple non-contiguous slices. However, I run into an issue as the compiler can't seem to figure out if my slices are 1D or 2D when it comes to taking a dot product and I'm not sure how to specify this information.
Here is a simplified example:
use ndarray::prelude::*;
fn main() {
let a = array![[1, 2, 3], [1, 2, 3]];
println!("{:?}", &a);
let b = array![[1, 2, 3], [1, 2, 3]];
println!("{:?}", &b);
let a_slice = a.slice(s![0, ..]);
let b_slice = b.slice(s![0, ..]);
println!("{:?}", &a_slice.dot(&b_slice));
}
The error message is a bit confusing, and I don't know how to make the dot product use the right candidate given the abstracted structs involved:
error[E0034]: multiple applicable items in scope
--> src/main.rs:14:31
|
14 | println!("{:?}", &a_slice.dot(&b_slice));
| ^^^ multiple `dot` found
|
= note: candidate #1 is defined in an impl for the type `ndarray::ArrayBase<_, ndarray::dimension::dim::Dim<[usize; 1]>>`
= note: candidate #2 is defined in an impl for the type `ndarray::ArrayBase<_, ndarray::dimension::dim::Dim<[usize; 2]>>`
How do I provide the necessary information?
Despite the error message referring to ArrayBase, the important piece of information here is actually the dimensions of the slice. So providing explicit dimensions using ArrayView does the trick:
use ndarray::prelude::*;
fn main() {
let a = array![[1, 2, 3], [1, 2, 3]];
println!("{:?}", &a);
let b = array![[1, 2, 3], [1, 2, 3]];
println!("{:?}", &b);
let a_slice: ArrayView<_, Ix1> = a.slice(s![0, ..]);
let b_slice: ArrayView<_, Ix1> = b.slice(s![0, ..]);
println!("{:?}", &a_slice.dot(&b_slice));
}
I am currently looking at the following code (which can be found here)
void MPU6050::CalibrateAccel(uint8_t Loops,uint8_t OffsetSaveAddress) {
double kP = 0.15;
double kI = 8;
float x;
x = (100 - map(Loops, 1, 5, 20, 0)) * .01;
kP *= x;
kI *= x;
PID( 0x3B, OffsetSaveAddress, kP, kI, Loops);
}
Specifically I am struggling to understand what the line:
x = (100 - map(Loops, 1, 5, 20, 0)) * .01;
is doing?
The best matching function I can find for map() is here but it doesn't appear to match the integer parameters that are being passed into the function.
Obviously ideally I would run this code but unfortunately I am yet unable to get this to compile.
Have I correctly found the function being invoked and what is the behaviour of this function with the given parameters? I assume this is a map() function similar to any other typical map function in other languages/frameworks such as python, jquery etc.
Could anyone guide me in the right direction?
map defined in math, re-maps a number from one range to another. Syntax is map(value, fromLow, fromHigh, toLow, toHigh)
So, map(Loops, 1, 5, 20, 0)) means the value of variable Loops will be initially searched between (1,5) but result will be between 20 to 0 since it is remapped.
Right now I have an SML function:
method([1,1,1,1,2,2,2,3,3,3]);
returns:
val it = [[2,2,2],[3,3,3]] : int list list
but I need it to return:
val it = [[1,1,1,1],[2,2,2],[3,3,3]] : int list list
This is my current code:
- fun method2(L: int list) =
= if tl(L) = [] then [hd(L)] else
= if hd(tl(L)) = hd(L) then hd(L)::method(tl(L)) else [hd(L)];
- fun method(L: int list) =
= if tl(L) = [] then [] else
= if hd(tl(L)) = hd(L) then method(tl(L)) else
= method2(tl(L))::method(tl(L));
As you can see it misses the first method2 call. Any ideas on how I can fix this? I am completely stumped.
Your problem is here if hd(tl(L)) = hd(L) then method(tl(L)) else. This is saying if the head of the tail is equal to the head, then continue processing, but don't add it to the result list. this will skip the first contiguous chunk of equal values. I would suggest separating the duties of these functions a bit more. The way to do this is to have method2 strip off the next contiguous chunk of values, and return a pair, where the first element will have the contiguous chunk removed, and the second element will have the remaining list. For example, method2([1, 1, 1, 2, 2, 3, 3]) = ([1, 1, 1], [2, 2, 3, 3]) and method2([2, 2, 3, 3]) = ([2, 2], [3, 3]). Now, you can just keep calling method2 until the second part of the pair is nil.
I'm not quite sure what you are trying to do with your code. I would recommend creating a tail recursive helper function which is passed three arguments:
1) The list of lists you are trying to build up
2) The current list you are building up
3) The list you are processing
In your example, a typical call somewhere in the middle of the computation would look like:
helper([[1,1,1,1]], [2,2],[2,3,3,3])
The recursion would work by looking at the head of the last argument ([2,3,3,3]) as well as the head of the list which is currently being built up ([2,2]) and, since they are the same -- the 2 at the end of the last argument is shunted to the list being built up:
helper([[1,1,1,1]], [2,2,2],[3,3,3])
in the next step in the recursion the heads are then compared and found to be different (2 != 3), so the helper function will put the middle list at the front of the list of lists:
helper([[2,2,2], [1,1,1,1]], [3],[3,3])
the middle list is re-initialized to [3] so it will start growing
eventually you reach something like this:
helper([[2,2,2], [1,1,1,1]], [3,3,3],[])
the [3,3,3] is then tacked onto the list of lists and the reverse of this list is returned.
Once such a helper function is defined, the main method checks for an empty list and, if not empty, initializes the first call to the helper function. The following code fleshes out theses ideas -- using pattern-matching style rather than hd and tl (I am not a big fan of using those functions explicitly -- it makes the code too Lisp-like). If this is homework then you should probably thoroughly understand how it works and then translate it to code involving hd and tl since your professor would regard it as plagiarized if you use things you haven't yet studied and haven't made it your own work:
fun helper (xs, ys, []) = rev (ys::xs)
| helper (xs, y::ys, z::zs) =
if y = z
then helper(xs, z :: y :: ys, zs)
else helper((y::ys)::xs,[z],zs);
fun method [] = []
| method (x::xs) = helper([],[x],xs);
I have a list A, and a function f which takes an item of A and returns a list. I can use a list comprehension to convert everything in A like [f(a) for a in A], but this returns a list of lists. Suppose my input is [a1,a2,a3], resulting in [[b11,b12],[b21,b22],[b31,b32]].
How can I get the flattened list [b11,b12,b21,b22,b31,b32] instead? In other words, in Python, how can I get what is traditionally called flatmap in functional programming languages, or SelectMany in .NET?
(In the actual code, A is a list of directories, and f is os.listdir. I want to build a flat list of subdirectories.)
See also: How do I make a flat list out of a list of lists? for the more general problem of flattening a list of lists after it's been created.
You can have nested iterations in a single list comprehension:
[filename for path in dirs for filename in os.listdir(path)]
which is equivalent (at least functionally) to:
filenames = []
for path in dirs:
for filename in os.listdir(path):
filenames.append(filename)
>>> from functools import reduce # not needed on Python 2
>>> list_of_lists = [[1, 2],[3, 4, 5], [6]]
>>> reduce(list.__add__, list_of_lists)
[1, 2, 3, 4, 5, 6]
The itertools solution is more efficient, but this feels very pythonic.
You can find a good answer in the itertools recipes:
import itertools
def flatten(list_of_lists):
return list(itertools.chain.from_iterable(list_of_lists))
The question proposed flatmap. Some implementations are proposed but they may unnecessary creating intermediate lists. Here is one implementation that's based on iterators.
def flatmap(func, *iterable):
return itertools.chain.from_iterable(map(func, *iterable))
In [148]: list(flatmap(os.listdir, ['c:/mfg','c:/Intel']))
Out[148]: ['SPEC.pdf', 'W7ADD64EN006.cdr', 'W7ADD64EN006.pdf', 'ExtremeGraphics', 'Logs']
In Python 2.x, use itertools.map in place of map.
You could just do the straightforward:
subs = []
for d in dirs:
subs.extend(os.listdir(d))
You can concatenate lists using the normal addition operator:
>>> [1, 2] + [3, 4]
[1, 2, 3, 4]
The built-in function sum will add the numbers in a sequence and can optionally start from a specific value:
>>> sum(xrange(10), 100)
145
Combine the above to flatten a list of lists:
>>> sum([[1, 2], [3, 4]], [])
[1, 2, 3, 4]
You can now define your flatmap:
>>> def flatmap(f, seq):
... return sum([f(s) for s in seq], [])
...
>>> flatmap(range, [1,2,3])
[0, 0, 1, 0, 1, 2]
Edit: I just saw the critique in the comments for another answer and I guess it is correct that Python will needlessly build and garbage collect lots of smaller lists with this solution. So the best thing that can be said about it is that it is very simple and concise if you're used to functional programming :-)
subs = []
map(subs.extend, (os.listdir(d) for d in dirs))
(but Ants's answer is better; +1 for him)
import itertools
x=[['b11','b12'],['b21','b22'],['b31']]
y=list(itertools.chain(*x))
print y
itertools will work from python2.3 and greater
You could try itertools.chain(), like this:
import itertools
import os
dirs = ["c:\\usr", "c:\\temp"]
subs = list(itertools.chain(*[os.listdir(d) for d in dirs]))
print subs
itertools.chain() returns an iterator, hence the passing to list().
This is the most simple way to do it:
def flatMap(array):
return reduce(lambda a,b: a+b, array)
The 'a+b' refers to concatenation of two lists
You can use pyxtension:
from pyxtension.streams import stream
stream([ [1,2,3], [4,5], [], [6] ]).flatMap() == range(7)
Google brought me next solution:
def flatten(l):
if isinstance(l,list):
return sum(map(flatten,l))
else:
return l
If listA=[list1,list2,list3]
flattened_list=reduce(lambda x,y:x+y,listA)
This will do.