Nesting displayed when mapping collection in HAML - html

I'm working on a Rails application, using HAML. I needed to present a list to the user, when I stumbled upon this weird behaviour:
This code generates no output, obviously.
- 5.times.map do |n|
- n + 1
This code generates 1 2 3 4 5, as it's outputting the return value for the inner statement.
- 5.times.map do |n|
= n + 1 # Notice the =
This code generates [1, 2, 3, 4, 5], as it's outputting the mapped array.
= 5.times.map do |n| # Notice the =
- n + 1
So far, so good...
This code generates 1 2 3 4 5 [0, 0, 0, 0, 0], as it doesn't like humans.
= 5.times.map do |n| # Notice the =
= n + 1 # on both lines
This code generates 1 2 3 4 5 [1, 1, 1, 1, 1], as it's nested in the <p>.
%p # Here we are nested one <p> deep
= 5.times.map do |n|
= n + 1
This code generates 1 2 3 4 5 [2, 2, 2, 2, 2], as it's double nested (and so on).
%p # Here we are nested two <p> deep
%p
= 5.times.map do |n|
= n + 1
Does anyone have an explanation for what is happening here in the HAML internals? Is = n+1 both adding a string to some output buffer, and then returning it's nesting level?

Actually, what you are asking is very version dependent. I mean it will produce different results in upcoming version of Haml (v4.1.0).
But first let's find out why this output in version < 4.1.0?
Under the hood, Haml translates templates into executable Ruby code. This job is done in Haml::Compiler class and you can easily debug this code:
require 'haml'
puts Haml::Engine.new(%q{
%p
%p
%p
= 5.times.map do |i|
= i
}).render # => produces the output
# you have in last example
to find out what the associated Ruby code looks like.
With some simplifications, it looks like this:
haml_temp = 5.times.map do |i|
_hamlout.push_text(" #{_hamlout.format_script_false_false_false_false_false_true_false(( i ));}\n", 0, false);
end
You can easily see by now, that your values are mapped to the expression:
_hamlout.push_text(...)
_hamlout here is an instance of Haml::Buffer and this is the last line, which returns value from the push_text method:
#real_tabs += tab_change
#real_tabs is indent level. If we use no indent, then it's 0, when one %p is involved, it becomes 1 and so on. tab_change argument is 0 (see debugged code).
So the output for Haml version 4.0.7 is equal to the level of nesting. This is exactly how your output looks like.
But this behavior will be probably "broken" in upcoming version 4.1.0. Compare the last line of the same method from current master branch:
#buffer << text
which will return some textual value.

Related

Csv reader PyQt5 [duplicate]

I've an iterable list of over 100 elements. I want to do something after every 10th iterable element. I don't want to use a counter variable. I'm looking for some solution which does not includes a counter variable.
Currently I do like this:
count = 0
for i in range(0,len(mylist)):
if count == 10:
count = 0
#do something
print i
count += 1
Is there some way in which I can omit counter variable?
for count, element in enumerate(mylist, 1): # Start counting from 1
if count % 10 == 0:
# do something
Use enumerate. Its built for this
Just to show another option...hopefully I understood your question correctly...slicing will give you exactly the elements of the list that you want without having to to loop through every element or keep any enumerations or counters. See Explain Python's slice notation.
If you want to start on the 1st element and get every 10th element from that point:
# 1st element, 11th element, 21st element, etc. (index 0, index 10, index 20, etc.)
for e in myList[::10]:
<do something>
If you want to start on the 10th element and get every 10th element from that point:
# 10th element, 20th element, 30th element, etc. (index 9, index 19, index 29, etc.)
for e in myList[9::10]:
<do something>
Example of the 2nd option (Python 2):
myList = range(1, 101) # list(range(1, 101)) for Python 3 if you need a list
for e in myList[9::10]:
print e # print(e) for Python 3
Prints:
10
20
30
...etc...
100
for i in range(0,len(mylist)):
if (i+1)%10==0:
do something
print i
A different way to approach the problem is to split the iterable into your chunks before you start processing them.
The grouper recipe does exactly this:
from itertools import izip_longest # needed for grouper
def grouper(iterable, n, fillvalue=None):
"Collect data into fixed-length chunks or blocks"
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)
You would use it like this:
>>> i = [1,2,3,4,5,6,7,8]
>>> by_twos = list(grouper(i, 2))
>>> by_twos
[(1, 2), (3, 4), (5, 6), (7, 8)]
Now, simply loop over the by_twos list.
You can use range loops to iterate through the length of mylist in multiples of 10 the following way:
for i in range(0,len(mylist), 10):
#do something

Why am I getting out of range error?

I'm building a function to extract all negatives from the list xs. Then I'm appending those negatives to a list negatives, and adding list negatives to list new_home, which may or may not already have values to it. The function was working before I added xs.pop(num). Why is it now out of range?
Here is the code:
def extract_negatives(xs,new_home=None):
negatives=[]
if new_home==None:
for num in range(len(xs)):
if xs[num] <0:
negatives.append(xs[num])
xs.pop(num)
return negatives
else:
for num in range(len(xs)):
if xs[num] <0:
new_home.append(xs[num])
xs.pop(num)
return new_home.append(negatives)
As stated, you are mutating list passed to the function and hence index is getting messed up.
If you must delete from passed list then one idea is to delete at the end of function just before you returned result. That way mutation wont affect indexing.
Also , I don't understand why you have if and else both looking for xs<0 . I cleaned up your function and got it working.
EDIT1 -Working Code
def extract_negatives(xs,new_home):
negatives=[]
for num in range(len(xs)):
if xs[num] <0:
negatives.append(xs[num])
new_home = new_home + negatives
for i in negatives:
xs.remove(i)
return new_home
new_home=[-9,-11,]
xs = [ 2 ,-3, 4, -5, 6, -7]
new_home = extract_negatives(xs,new_home)
print new_home
Output
>>>
[-9, -11, -3, -5, -7]
>>> xs
[2, 4, 6]

Egg dropping in worst case

I have been trying to write an algorithm to compute the maximum number or trials required in worst case, in the egg dropping problem. Here is my python code
def eggDrop(n,k):
eggFloor=[ [0 for i in range(k+1) ] ]* (n+1)
for i in range(1, n+1):
eggFloor[i][1] = 1
eggFloor[i][0] = 0
for j in range(1, k+1):
eggFloor[1][j] = j
for i in range (2, n+1):
for j in range (2, k+1):
eggFloor[i][j] = 'infinity'
for x in range (1, j + 1):
res = 1 + max(eggFloor[i-1][x-1], eggFloor[i][j-x])
if res < eggFloor[i][j]:
eggFloor[i][j] = res
return eggFloor[n][k]print eggDrop(2, 100)
```
The code is outputting a value of 7 for 2eggs and 100floors, but the answer should be 14, i don't know what mistake i have made in the code. What is the problem?
The problem is in this line:
eggFloor=[ [0 for i in range(k+1) ] ]* (n+1)
You want this to create a list containing (n+1) lists of (k+1) zeroes. What the * (n+1) does is slightly different - it creates a list containing (n+1) copies of the same list.
This is an important distinction - because when you start modifying entries in the list - say,
eggFloor[i][1] = 1
this actually changes element [1] of all of the lists, not just the ith one.
To instead create separate lists that can be modified independently, you want something like:
eggFloor=[ [0 for i in range(k+1) ] for j in range(n+1) ]
With this modification, the program returns 14 as expected.
(To debug this, it might have been a good idea to write out a function to pring out the eggFloor array, and display it at various points in your program, so you can compare it with what you were expecting. It would soon become pretty clear what was going on!)

find function matlab in numpy/scipy

Is there an equivalent function of find(A>9,1) from matlab for numpy/scipy. I know that there is the nonzero function in numpy but what I need is the first index so that I can use the first index in another extracted column.
Ex: A = [ 1 2 3 9 6 4 3 10 ]
find(A>9,1) would return index 4 in matlab
The equivalent of find in numpy is nonzero, but it does not support a second parameter.
But you can do something like this to get the behavior you are looking for.
B = nonzero(A >= 9)[0]
But if all you are looking for is finding the first element that satisfies a condition, you are better off using max.
For example, in matlab, find(A >= 9, 1) would be the same as [~, idx] = max(A >= 9). The equivalent function in numpy would be the following.
idx = (A >= 9).argmax()
matlab's find(X, K) is roughly equivalent to numpy.nonzero(X)[0][:K] in python. #Pavan's argmax method is probably a good option if K == 1, but unless you know apriori that there will be a value in A >= 9, you will probably need to do something like:
idx = (A >= 9).argmax()
if (idx == 0) and (A[0] < 9):
# No value in A is >= 9
...
I'm sure these are all great answers but I wasn't able to make use of them. However, I found another thread that partially answers this:
MATLAB-style find() function in Python
John posted the following code that accounts for the first argument of find, in your case A>9 ---find(A>9,1)-- but not the second argument.
I altered John's code which I believe accounts for the second argument ",1"
def indices(a, func):
return [i for (i, val) in enumerate(a) if func(val)]
a = [1,2,3,9,6,4,3,10]
threshold = indices(a, lambda y: y >= 9)[0]
This returns threshold=3. My understanding is that Python's index starts at 0... so it's the equivalent of matlab saying 4. You can change the value of the index being called by changing the number in the brackets ie [1], [2], etc instead of [0].
John's original code:
def indices(a, func):
return [i for (i, val) in enumerate(a) if func(val)]
a = [1, 2, 3, 1, 2, 3, 1, 2, 3]
inds = indices(a, lambda x: x > 2)
which returns >>> inds [2, 5, 8]
Consider using argwhere in Python to replace MATLAB's find function. For example,
import numpy as np
A = [1, 2, 3, 9, 6, 4, 3, 10]
np.argwhere(np.asarray(A)>=9)[0][0] # Return first index
returns 3.
import numpy
A = numpy.array([1, 2, 3, 9, 6, 4, 3, 10])
index = numpy.where(A >= 9)
You can do this by first convert the list to an ndarray, then using the function numpy.where() to get the desired index.

What's the correct way to expand a [0,1] interval to [a,b]?

Many random-number generators return floating numbers between 0 and 1.
What's the best and correct way to get integers between a and b?
Divide the interval [0,1] in B-A+1 bins
Example A=2, B=5
[----+----+----+----]
0 1/4 1/2 3/4 1
Maps to 2 3 4 5
The problem with the formula
Int (Rnd() * (B-A+1)) + A
is that your Rnd() generation interval is closed on both sides, thus the 0 and the 1 are both possible outputs and the formula gives 6 when the Rnd() is exactly 1.
In a real random distribution (not pseudo), the 1 has probability zero. I think it is safe enough to program something like:
r=Rnd()
if r equal 1
MyInt = B
else
MyInt = Int(r * (B-A+1)) + A
endif
Edit
Just a quick test in Mathematica:
Define our function:
f[a_, b_] := If[(r = RandomReal[]) == 1, b, IntegerPart[r (b - a + 1)] + a]
Build a table with 3 10^5 numbers in [1,100]:
table = SortBy[Tally[Table[f[1, 100], {300000}]], First]
Check minimum and maximum:
In[137]:= {Max[First /# table], Min[First /# table]}
Out[137]= {100, 1}
Lets see the distribution:
BarChart[Last /# SortBy[Tally[Table[f[1, 100], {300000}]], First],
ChartStyle -> "DarkRainbow"]
X = (Rand() * (B - A)) + A
Another way to look at it, where r is your random number in the range 0 to 1:
(1-r)a + rb
As for your additional requirement of the result being an integer, maybe (apart from using built in casting) the modulus operator can help you out. Check out this question and the answer:
Expand a random range from 1–5 to 1–7
Well, why not just look at how Python does it itself? Read random.py in your installation's lib directory.
After gutting it to only support the behavior of random.randint() (which is what you want) and removing all error checks for non-integer or out-of-bounds arguments, you get:
import random
def randint(start, stop):
width = stop+1 - start
return start + int(random.random()*width)
Testing:
>>> l = []
>>> for i in range(2000000):
... l.append(randint(3,6))
...
>>> l.count(3)
499593
>>> l.count(4)
499359
>>> l.count(5)
501432
>>> l.count(6)
499616
>>>
Assuming r_a_b is the desired random number between a and b and r_0_1 is a random number between 0 and 1 the following should work just fine:
r_a_b = (r_0_1 * (b-a)) + a