Haskell nested where clauses - haskell

I am a beginner coder in haskell, while doing an exercise from the first chapter of this amazing book: http://book.realworldhaskell.org/read/getting-started.html
I came across this issue:
-- test comment
main = interact wordCount
where
wordCount input = show (ls ++ " " ++ ws ++ " " ++ cs ++ "\n")
where
ls = lines input
ws = length words input
cs = length input
wonderbox:ch01 manasapte$ runghc WC < quux.txt
WC.hs:5:9: parse error on input ‘where’
Why can I not nest my wheres ?

Since your second where is attached to the wordCount definition, it needs to be indented more than it. (Although you will still have some other errors afterward.)

Others have already answered. I will just add some more explanation.
Simplifying a bit, the Haskell indentation rule is:
Some keywords start a block of things (where,let,do,case ... of).
Find the first word after such keywords and note its indentation. Name the column it occurs the pivot column.
Start a line exactly on the pivot to define a new entry in the block.
Start a line after the pivot to continue the entry started in the previous lines.
Start a line before the pivot to end the block.
Hence,
where
wordCount input = show (ls ++ " " ++ ws ++ " " ++ cs ++ "\n")
where
ls = lines input
ws = length words input
cs = length input
Actually means
where {
wordCount input = show (ls ++ " " ++ ws ++ " " ++ cs ++ "\n")
;
where { -- same column, new entry
ls = lines input
; -- same column, new entry
ws = length words input
; -- same column, new entry
cs = length input
}
}
which treats the second where as a separate definition unrelated to wordCount. If we indent it more, it will work:
where {
wordCount input = show (ls ++ " " ++ ws ++ " " ++ cs ++ "\n")
where { -- after the pivot, same entry
ls = lines input
;
ws = length words input
;
cs = length input
}
}

the indentation was incorrect, here's the working version:
-- test comment
import Data.List
main = interact wordCount
where wordCount input = unlines $ [concat $ intersperse " " (map show [ls, ws, cs])]
where ls = length $ lines input
ws = length $ words input
cs = length input

Related

how to properly remove banned words?

I have a line from which I want to remove all words beginning with the symbol #, I do not fully understand how to do it expressively. It is clear that you could write something like this:
Split the string into words
Use the list filter to weed out unnecessary words
But I guess I don't understand how to break lines, because in addition to the space, there are such characters as \t and \n, besides, I will lose them and can not restore the original text.
An example of what I want to get:
original string:
haha lala\n#delete_me all-ok
expected result:
haha lala\nall-ok
You might want to use Data.List.Split.split with Data.List.Split.oneOf.
It returns split words including separators, so you can rebuild text with them.
split (oneOf "xyz") "aazbxyzcxd" == ["aa","z","b","x","","y","","z","c","x","d"]
Another way to look at the problem is that we want to delete strings of non-spaces that begin with an at sign #, as well as any following spaces. We don’t want to treat line breaks or other characters specially at all. That can be expressed with a simple recursive function using span / break and dropWhile:
censor :: String -> String
censor "" = ""
censor text0 = spaces ++ nonspaces ++ censor rest
where
(spaces, text1) = span isSpace text0
(word, text2) = break isSpace text1
(nonspaces, rest)
| banned word
= ("", trim text2)
| otherwise
= (word, text2)
banned :: String -> Bool
banned ('#' : _) = True
banned _ = False
trim :: String -> String
trim = dropWhile isSpace
Consider an example:
censor " send #beans money to sam#example.com"
span returns " " and "send #beans…"
break returns "send" and " #beans…"
banned returns false for "send", so we will keep it
We recursively call censor " #beans money…"
span returns " " and "#beans money…"
break returns "#beans" and " money…"
Now banned returns true for "#beans", so we drop it and trim the rest
We recursively call censor "money…"
We keep all the remaining substrings, including sam#example.com, since it is not banned
Finally, we reach the end of the string and censor "" returns ""
The end result is this expression:
" " ++ "send" ++ " " ++ "" ++ "money" ++ " " ++ "to" ++ " " ++ "sam#example.com" ++ ""
Notice that we use a series of updates to the input string resulting in a series of variables text0, text1, text2, rest for the intermediate states. Consider how you could express this pattern using State instead.

How to remove the last space from end when using loop in python?

I am trying to make a program to print a list separated with space but I do not want the space in the last ?
I tried using string method but want more efficient way to do it.
There is not a lot to go on with the information you have provided but from what I understand may be this could help:-
l = ["Hello", "this", "is", "stackoverflow"]
n = len(l)
for i, ele in enumerate(l) :
x = " "
if i == n-1 :
x = ""
print(ele,end = x)

How to change an element in [String] in Haskell?

I'm working on a program that receives as input a board game as follows:
#####
#_ ##
# ##
# #
# .#
#####
1 4 (player initial position, marked with '_')
After receiving the input, the program transforms it to a [String].
This case, it would be:
["#####", "#_ ##", "# ##", "# #", "# .#", "#####", "1 4"]
How can I access position [1,4] and transform '_' to 'o'?
Function must return initial list with that transformation.
Very important note: '_' is never displayed on input, I only used it to make clear where position [1,4] is (therefore, on input we only see a blank space, ' ')
Seems like one of those tasks you might have to solve for online coding games. As others pointed out, lists are not really suited for dealing with coordinates like this. However, if you are not able to use better libraries (like in coding games) you will have to do some more work.
Here is the code from my ghci session (transforming to a proper program is left as an exercise for the reader...):
let input = ["#####", "#_ ##", "# ##", "# #", "# .#", "#####", "1 4"]
let reverseInput = reverse input
let position = head reverseInput
let board = tail reverseInput
let posX = read $ takeWhile (/=' ') position :: Int
let posY = read $ takeWhile (/=' ') $ reverse position :: Int
let (unchangedBoard, changedBoard) = splitAt posY board
let (unchangedRow, changedRow) = splitAt posX $ head changedBoard
let newRow = unchangedRow ++ "o" ++ tail changedRow
let newBoard = unchangedBoard ++ [newRow] ++ tail changedBoard
let finalOutput = reverse newBoard
mapM_ putStrLn finalOutput
Also note this code is very brittle as it uses partial functions all over the place (tail, head, read). You could try to use pattern matching instead to make the code more robust.

Tuples in Haskell

I'd like to write a function that combines a unique-name in list u with a verb in list v with another unique-name in u so that i'd get 3 outputs like this:
[ ("fluffy", "loves", "monkey"), ("bunny", "feeds", "fluffy"),
("bunny", "feeds", "monkey") ]
The only thing is I don't know how to get it so that the second unique-name it gives me is different than the first. Here's my code:
let fun = [ u ++ " " ++ v ++ " " ++ u | u <- ["fluffy", "bunny", "monkey"], v <- ["eats", "feeds", "loves"]]
let funThree = take 3 (cycle fun)
Currently my output is this:
["fluffy eats fluffy","fluffy feeds fluffy","fluffy loves fluffy"]
First, let's define the names so we can use them more than once
> let names = ["fluffy", "bunny", "monkey"]
We can use names twice in a list comprehension, once to get the first unique-name u1, and again to get a second name, u2.
> let fun = [ u1 ++ " " ++ v ++ " " ++ u2 | u1 <- names, v <- ["eats", "feeds", "loves"], u2 <- names]
This results in 27 different strings
> fun
["fluffy eats fluffy","fluffy eats bunny","fluffy eats monkey","fluffy feeds fluffy","fluffy feeds bunny","fluffy feeds monkey","bunny eats fluffy","bunny eats bunny","bunny eats monkey","bunny feeds fluffy","bunny feeds bunny","bunny feeds monkey","monkey eats fluffy","monkey eats bunny","monkey eats monkey","monkey feeds fluffy","monkey feeds bunny","monkey feeds monkey"]
If "fluffy eats fluffy" is a little too wierd, we can make sure the two names are different by adding a condition, u1 /= u2.
> let fun = [ u1 ++ " " ++ v ++ " " ++ u2 | u1 <- names, v <- ["eats", "feeds", "loves"], u2 <- names, u1 /= u2]
This results in only 18 different strings.
> fun
["fluffy eats bunny","fluffy eats monkey","fluffy feeds bunny","fluffy feeds monkey","bunny eats fluffy","bunny eats monkey","bunny feeds fluffy","bunny feeds monkey","monkey eats fluffy","monkey eats bunny","monkey feeds fluffy","monkey feeds bunny"]

Import multiline SQL query to single string

In R, how can I import the contents of a multiline text file (containing SQL) to a single string?
The sql.txt file looks like this:
SELECT TOP 100
setpoint,
tph
FROM rates
I need to import that text file into an R string such that it looks like this:
> sqlString
[1] "SELECT TOP 100 setpoint, tph FROM rates"
That's so that I can feed it to the RODBC like this
> library(RODBC)
> myconn<-odbcConnect("RPM")
> results<-sqlQuery(myconn,sqlString)
I've tried the readLines command as follows but it doesn't give the string format that RODBC needs.
> filecon<-file("sql.txt","r")
> sqlString<-readLines(filecon, warn=FALSE)
> sqlString
[1] "SELECT TOP 100 " "\t[Reclaim Setpoint Mean (tph)] as setpoint, "
[3] "\t[Reclaim Rate Mean (tph)] as tphmean " "FROM [Dampier_RC1P].[dbo].[Rates]"
>
The versatile paste() command can do that with argument collapse="":
lines <- readLines("/tmp/sql.txt")
lines
[1] "SELECT TOP 100 " " setpoint, " " tph " "FROM rates"
sqlcmd <- paste(lines, collapse="")
sqlcmd
[1] "SELECT TOP 100 setpoint, tph FROM rates"
Below is an R function that reads in a multiline SQL query (from a text file) and converts it into a single-line string. The function removes formatting and whole-line comments.
To use it, run the code to define the functions, and your single-line string will be the result of running
ONELINEQ("querytextfile.sql","~/path/to/thefile").
How it works: Inline comments detail this; it reads each line of the query and deletes (replaces with nothing) whatever isn't needed to write out a single-line version of the query (as asked for in the question). The result is a list of lines, some of which are blank and get filtered out; the last step is to paste this (unlisted) list together and return the single line.
#
# This set of functions allows us to read in formatted, commented SQL queries
# Comments must be entire-line comments, not on same line as SQL code, and begun with "--"
# The parsing function, to be applied to each line:
LINECLEAN <- function(x) {
x = gsub("\t+", "", x, perl=TRUE); # remove all tabs
x = gsub("^\\s+", "", x, perl=TRUE); # remove leading whitespace
x = gsub("\\s+$", "", x, perl=TRUE); # remove trailing whitespace
x = gsub("[ ]+", " ", x, perl=TRUE); # collapse multiple spaces to a single space
x = gsub("^[--]+.*$", "", x, perl=TRUE); # destroy any comments
return(x)
}
# PRETTYQUERY is the filename of your formatted query in quotes, eg "myquery.sql"
# DIRPATH is the path to that file, eg "~/Documents/queries"
ONELINEQ <- function(PRETTYQUERY,DIRPATH) {
A <- readLines(paste0(DIRPATH,"/",PRETTYQUERY)) # read in the query to a list of lines
B <- lapply(A,LINECLEAN) # process each line
C <- Filter(function(x) x != "",B) # remove blank and/or comment lines
D <- paste(unlist(C),collapse=" ") # paste lines together into one-line string, spaces between.
return(D)
}
# TODO: add eof newline automatically to remove warning
#############################################################################################
Here's the final version of what I'm using. Thanks Dirk.
fileconn<-file("sql.txt","r")
sqlString<-readLines(fileconn)
sqlString<-paste(sqlString,collapse="")
gsub("\t","", sqlString)
library(RODBC)
sqlconn<-odbcConnect("RPM")
results<-sqlQuery(sqlconn,sqlString)
library(qcc)
tph <- qcc(results$tphmean[1:50], type="xbar.one", ylim=c(4000,12000), std.dev=600)
close(fileconn)
close(sqlconn)
This is what I use:
# Set Filename
fileName <- 'Input File.txt'
doSub <- function(src, dest_var_name, src_pattern, dest_pattern) {
assign(
x = dest_var_name
, value = gsub(
pattern = src_pattern
, replacement = dest_pattern
, x = src
)
, envir = .GlobalEnv
)
}
# Read File Contents
original_text <- readChar(fileName, file.info(fileName)$size)
# Convert to UNIX line ending for ease of use
doSub(src = original_text, dest_var_name = 'unix_text', src_pattern = '\r\n', dest_pattern = '\n')
# Remove Block Comments
doSub(src = unix_text, dest_var_name = 'wo_bc_text', src_pattern = '/\\*.*?\\*/', dest_pattern = '')
# Remove Line Comments
doSub(src = wo_bc_text, dest_var_name = 'wo_bc_lc_text', src_pattern = '--.*?\n', dest_pattern = '')
# Remove Line Endings to get Flat Text
doSub(src = wo_bc_lc_text, dest_var_name = 'flat_text', src_pattern = '\n', dest_pattern = ' ')
# Remove Contiguous Spaces
doSub(src = flat_text, dest_var_name = 'clean_flat_text', src_pattern = ' +', dest_pattern = ' ')
try paste(sqlString, collapse=" ")
It's possible to use readChar() instead of readLines(). I had an ongoing issue with mixed commenting (-- or /* */) and this has always worked well for me.
sql <- readChar(path.to.file, file.size(path.to.file))
query <- sqlQuery(con, sql, stringsAsFactors = TRUE)
I use sql <- gsub("\n","",sql) and sql <- gsub("\t","",sql) together.

Resources