【问题标题】:How to add tuples to list after reading from a text file in Haskell从 Haskell 中的文本文件读取后如何将元组添加到列表中
【发布时间】:2015-05-05 12:26:34
【问题描述】:

我正在尝试在 Haskell 中创建一个程序,该程序从文本文件中读取文本并将它们添加到列表中。

我的想法是:

type x = [(String, Integer)] 

其中 String 是文本中的每个单词,Integer 是该单词在文本中出现的次数。所以我想创建这些值的元组并将其添加到列表中。然后我想打印列表的内容。

我知道如何在 Haskell 中读取文本文件,但不确定下一步该做什么。我是 Haskell 编程的新手,主要是用 Java 编程,这是非常不同的。

编辑:

这就是我到目前为止的建议。我可以使用从文件中接收到的文本写入输出文本文件并将其设为小写。我遇到的问题是使用其他功能,因为它说:

Test.hs:14:59: Not in scope: ‘group’

代码如下:

import System.IO  
import Data.Char(toLower)

main = do  
       contents <- readFile "testFile.txt"
       let lowContents = map toLower contents
       let outStr = countWords (lowContents)
       let finalStr = sortOccurrences (outStr)
       print outStr

-- Counts all the words
countWords :: String -> [(String, Int)]
countWords fileContents = countOccurrences (toWords fileContents)

-- Split words
toWords :: String -> [String]
toWords s = words s

-- Counts, how often each string in the given list appears
countOccurrences :: [String] -> [(String, Int)]
countOccurrences xs = map (\xs -> (head xs, length xs)) . group . sortOccurrences xs

-- Sort list in order of occurrences.
sortOccurrences :: [(String, Int)] -> [(String, Int)]
sortOccurrences sort = sortBy sort (comparing snd)

请任何人帮助我。

【问题讨论】:

  • 您应该将问题分解为更小的步骤。然后你可能想使用Data.Map
  • 首先请注意 x 不是有效的类型名称(需要大写)-我认为这可能是家庭作业(如果不是,我很抱歉)所以我不想要剧透太多,但是 list 在这里不是一个很好的选择(因为你必须经常更新它)——也许看看maps——你也一定想到了什么- 一点头绪都没有?
  • spoiler 如果地图太复杂,你可以改用 String -&gt; Integer 函数 - 你可以用 addOccurence f word = \w -&gt; if w == word then f w + 1 else f w 更新它 我发现这更容易开始(请注意这可能不是很有效 - 但它很简单) - 也寻找 folds (foldr, foldl, ...) - 也许你可以以某种方式使用它们

标签: list haskell tuples readfile


【解决方案1】:

Haskell 具有一个相当有表现力的类型系统(比 Java 更强大),因此最好以自上而下的方式纯粹从类型的角度来考虑这个问题。您提到您已经知道如何在 Haskell 中读取文本文件,所以我假设您知道如何获取包含文件内容的 String

您要定义的函数是这样的。现在,我们将定义设置为undefined,以便代码类型检查(但在运行时产生异常):

countWords :: String -> [(String, Int)]
countWords fileContents = undefined

您的函数将String(文件内容)映射到一个元组列表,每个元组将某个单词与该单词在输入中出现的频率相关联。这听起来像是解决方案的一部分将是一个可以将字符串拆分为单词列表的函数,这样您就可以处理它来计算单词。 IE。你会想要这样的东西:

-- Splits a string into a list of words
toWords :: String -> [String]
toWords s = undefined

-- Counts, how often each string in the given list appears
countOccurrences :: [String] -> [(String, Int)]
countOccurrences xs = undefined

有了这些,你就可以真正定义原来的函数了:

countWords :: String -> [(String, Int)]
countWords fileContents = countOccurrences (toWords fileContents)

您现在很好地将问题分解为两个子问题。

这个类型驱动程序的另一个优点是可以告诉Hoogle 去寻找给定类型的函数。例如,考虑一下我们之前描述的 toWords 函数的类型:

toWords :: String -> [String]
toWords s = undefined

Feeding this to Hoogle 揭示了一个不错的函数:words,这似乎正是我们想要的!所以我们可以定义

toWords :: String -> [String]
toWords s = words s

唯一缺少的是为countOccurrences 提供适当的定义。唉,Hoogle 上的searching for this type 没有显示任何现成的解决方案。但是,有三个函数对我们自己的定义很有用:sortgroupmap

  1. sort 函数的作用,顾名思义:它对事物列表进行排序:

    λ: sort [1,1,1,2,2,1,1,3,3]
    [1,1,1,1,1,2,2,3,3]
    
  2. group 函数将连续(!)个相等的元素组合在一起,产生一个列表列表。例如

    λ: group [1,1,1,1,1,2,2,3,3]
    [[1,1,1,1,1],[2,2],[3,3]]
    
  3. map 函数可用于将group 生成的列表列表转换为元组列表,并给出每个组的长度:

    λ: map (\xs -> (head xs, length xs)) [[1,1,1,1,1],[2,2],[3,3]]
    [(1,5),(2,2),(3,2)]
    

组合这三个函数可以定义

countOccurrences :: [String] -> [(String, Int)]
countOccurrences xs = map (\xs -> (head xs, length xs)) . group . sort $ xs

现在你已经准备好了所有的部分。您的countWords 是根据toWordscountOccurrences 定义的,每个都有适当的定义。

这种类型驱动方法的好处是写下函数签名将有助于您的思考和编译器(当您违反假设时会发现您)。您还可以自动将问题分解为更小的问题,您可以在ghci 中独立测试每个问题。

【讨论】:

  • 谢谢,这很有道理。我想创建我需要的类型,以便清楚地了解我需要做什么。
  • 我已经用我现在拥有的代码更新了我的问题,请你看看我哪里出错了。谢谢。
  • 我现在已经开始工作了。我必须导入 Data.List。另一个问题是元组是按单词排序的,我希望它们按出现次数排序。我该怎么做?
  • @JamesMeade:您可以应用相同的逻辑。如果您想按出现对单词进行排序,您可能正在寻找像sortByOcc :: [(String, Int)] -&gt; [(String, Int)] 这样的函数。在这种情况下,您可能会发现一个有用的函数是 sortBy
  • 我是这么认为的,这正是我尝试过的。我做到了:sortOccurrences :: [(String, Int)] -&gt; [(String, Int)]sortOccurrences sort = sortBy (comparing snd) 我只是不认为这个功能是正确的。你怎么看?
【解决方案2】:

Data.Map 是最简单的方法。

import qualified Data.Map as M

-- assuming you already have your list of words:
listOfWords :: [String]

-- you can generate your list of tuples with this
listOfTuples :: [(String, Integer)]
listOfTuples = M.toList . M.fromListWith (+) $ zip listOfWords (repeat 1)

【讨论】:

  • 这和Java中的Map,?>类似吗?
  • @JamesMeade 我想是的。 Data.Map 是从键到值的映射。 Map.fromListWith &lt;function&gt; 允许您从键、值元组 [(Key, Value)] 的列表中创建映射,并且您提供的函数是一个组合函数,用于在键冲突的情况下对值使用。所以在这种情况下,我们传入+,并使用键冲突来计算出现次数。
  • 我明白了,是的,这与 Java 中的 Map 格式相同。感谢您的澄清和建议。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-11
  • 2017-03-26
相关资源
最近更新 更多