Chris Hermansen 发布的文章

比较不同的编程语言如何解决同一个问题是一个很有趣的事情,也很有指导意义。接下来,我们就来讲一讲如何用 Python 来解决。

 title=

在我这一系列的 第一篇文章 里,我描述了这样子的一个问题,如何将一大批的救助物资分为具有相同价值的物品,并将其分发给社区中的困难住户。我也曾写过用不同的编程语言写一些小程序来解决这样子的小问题以及比较这些程序时如何工作的。

在第一篇文章中,我是使用了 Groovy 语言来解决问题的。Groovy 在很多方面都与 Python 很相似,但是在语法上她更像 C 语言和 Java。因此,使用 Python 来创造一个相同的解决方案应该会很有趣且更有意义。

使用 Python 的解决方案

使用 Java 时,我会声明一个工具类来保存元组数据(新的记录功能将会很好地用于这个需求)。使用 Groovy 时,我就是用了该语言的映射功能,我也将在 Python 使用相同的机制。

使用一个字典列表来保存从批发商处批发来的货物:

packs = [
        {'item':'Rice','brand':'Best Family','units':10,'price':5650,'quantity':1},
        {'item':'Spaghetti','brand':'Best Family','units':1,'price':327,'quantity':10},
        {'item':'Sardines','brand':'Fresh Caught','units':3,'price':2727,'quantity':3},
        {'item':'Chickpeas','brand':'Southern Style','units':2,'price':2600,'quantity':5},
        {'item':'Lentils','brand':'Southern Style','units':2,'price':2378,'quantity':5},
        {'item':'Vegetable oil','brand':'Crafco','units':12,'price':10020,'quantity':1},
        {'item':'UHT milk','brand':'Atlantic','units':6,'price':4560,'quantity':2},
        {'item':'Flour','brand':'Neighbor Mills','units':10,'price':5200,'quantity':1},
        {'item':'Tomato sauce','brand':'Best Family','units':1,'price':190,'quantity':10},
        {'item':'Sugar','brand':'Good Price','units':1,'price':565,'quantity':10},
        {'item':'Tea','brand':'Superior','units':5,'price':2720,'quantity':2},
        {'item':'Coffee','brand':'Colombia Select','units':2,'price':4180,'quantity':5},
        {'item':'Tofu','brand':'Gourmet Choice','units':1,'price':1580,'quantity':10},
        {'item':'Bleach','brand':'Blanchite','units':5,'price':3550,'quantity':2},
        {'item':'Soap','brand':'Sunny Day','units':6,'price':1794,'quantity':2}]

大米有一包,每包中有 10 袋大米,意大利面条有十包,每包中有一袋意大利面条。上述代码中,变量 packs 被设置为 Python 字典列表。这与 Groovy 的方法非常相似。关于 Groovy 和 Python 之间的区别,有几点需要注意:

  1. 在 Python 中,无需关键字来定义变量 packs,Python 变量初始化时需要设置一个值。
  2. Python 字典中的词键(例如,itembrandunitspricequantity)需要引号来表明它们是字符串;Groovy 假定这些是字符串,但也接受引号。
  3. 在 Python 中,符号 { ... } 表明一个字典声明; Groovy 使用与列表相同的方括号,但两种情况下的结构都必须具有键值对。

当然,表中的价格不是以美元计算的。

接下来,打开散装包。例如,打开大米的单个散装包装,将产出 10 单元大米; 也就是说,产出的单元总数是 units * quantity。 Groovy 脚本使用一个名为 collectMany 的方便的函数,该函数可用于展平列表列表。 据我所知,Python 没有类似的东西,所以使用两个列表推导式来产生相同的结果:

units = [[{'item':pack['item'],'brand':pack['brand'],
        'price':(pack['price'] / pack['units'])}] *
        (pack['units'] * pack['quantity']) for pack in packs]
units = [x for sublist in units for x in sublist]

第一个列表可理解为(分配给单元)构建字典列表列表。 第二个将其“扁平化”为字典列表。 请注意,Python 和 Groovy 都提供了一个 * 运算符,它接受左侧的列表和右侧的数字 N,并复制列表 N 次。

最后一步是将这些单元的大米之类的重新包装到篮子(hamper)中以进行分发。 就像在 Groovy 版本中一样,你需要更具体地了解理想的篮子数,当你只剩下几个单元时,你最好不要过度限制,即可以做一些随机分配:

valueIdeal = 5000
valueMax = valueIdeal * 1.1

很好! 重新打包篮子。

import random
hamperNumber = 0              # 导入 Python 的随机数生成器工具并初始化篮子数
while len(units) > 0:      # 只要有更多可用的单元,这个 `while` 循环就会将单元重新分配到篮子中:
    hamperNumber += 1
    hamper = []
    value = 0
    canAdd = True              # 增加篮子编号,得到一个新的空篮子(单元的列表),并将其值设为 0; 开始假设你可以向篮子中添加更多物品。
    while canAdd:              # 这个 `while` 循环将尽可能多地向篮子添加单元(Groovy 代码使用了 `for` 循环,但 Python 的 `for` 循环期望迭代某些东西,而 Groovy 则是为更传统的 C 形式的 `for` 循环形式):
        u = random.randint(0,len(units)-1)  # 获取一个介于 0 和剩余单元数减 1 之间的随机数。
        canAdd = False                      # 假设你找不到更多要添加的单元。
        o = 0                               # 创建一个变量,用于从你正在寻找要放入篮子中的物品的起点的偏移量。
        while o < len(units):               # 从随机选择的索引开始,这个 `while` 循环将尝试找到一个可以添加到篮子的单元(再次注意,Python `for` 循环可能不适合这里,因为列表的长度将在迭代中中发生变化)。
            uo = (u + o) % len(units)
            unit = units[uo]
            unitPrice = unit['price']          # 找出要查看的单元(随机起点+偏移量)并获得其价格。
            if len(units) < 3 or not (unit in hamper) and (value + unitPrice) < valueMax:
                                               # 如果只剩下几个,或者添加单元后篮子的价值不太高,你可以将此单元添加到篮子中。
                hamper.append(unit)
                value += unitPrice
                units.pop(u)                   # 将单元添加到篮子中,按单价增加 篮子数,从可用单元列表中删除该单元。
                canAdd = len(units) > 0
                break                          # 只要还有剩余单元,你就可以添加更多单元,因此可以跳出此循环继续寻找。
            o += 1                             # 增加偏移量。
                                            # 在退出这个 `while` 循环时,如果你检查了所有剩余的单元并且找不到单元可以添加到篮子中,那么篮子就完成了搜索; 否则,你找到了一个,可以继续寻找更多。
    print('')
    print('Hamper',hamperNumber,'value',value)
    for item in hamper:
        print('%-25s%-25s%7.2f' % (item['item'],item['brand'],item['price'])) # 打印出篮子的内容。
    print('Remaining units',len(units))                                       # 打印出剩余的单元信息。

一些澄清如上面的注释。

运行此代码时,输出看起来与 Groovy 程序的输出非常相似:

Hamper 1 value 5304.0
UHT milk                 Atlantic                  760.00
Tomato sauce             Best Family               190.00
Rice                     Best Family               565.00
Coffee                   Colombia Select          2090.00
Sugar                    Good Price                565.00
Vegetable oil            Crafco                    835.00
Soap                     Sunny Day                 299.00
Remaining units 148

Hamper 2 value 5428.0
Tea                      Superior                  544.00
Lentils                  Southern Style           1189.00
Flour                    Neighbor Mills            520.00
Tofu                     Gourmet Choice           1580.00
Vegetable oil            Crafco                    835.00
UHT milk                 Atlantic                  760.00
Remaining units 142

Hamper 3 value 5424.0
Soap                     Sunny Day                 299.00
Chickpeas                Southern Style           1300.00
Sardines                 Fresh Caught              909.00
Rice                     Best Family               565.00
Vegetable oil            Crafco                    835.00
Spaghetti                Best Family               327.00
Lentils                  Southern Style           1189.00
Remaining units 135

...

Hamper 21 value 5145.0
Tomato sauce             Best Family               190.00
Tea                      Superior                  544.00
Chickpeas                Southern Style           1300.00
Spaghetti                Best Family               327.00
UHT milk                 Atlantic                  760.00
Vegetable oil            Crafco                    835.00
Lentils                  Southern Style           1189.00
Remaining units 4

Hamper 22 value 2874.0
Sardines                 Fresh Caught              909.00
Vegetable oil            Crafco                    835.00
Rice                     Best Family               565.00
Rice                     Best Family               565.00
Remaining units 0

最后一个篮子在内容和价值上有所简化。

结论

乍一看,这个程序的 Python 和 Groovy 版本之间没有太大区别。 两者都有一组相似的结构,这使得处理列表和字典非常简单。 两者都不需要很多“样板代码”或其他“繁杂”操作。

此外,使用 Groovy 时,向篮子中添加单元还是一件比较繁琐的事情。 你需要在单元列表中随机选择一个位置,然后从该位置开始,遍历列表,直到找到一个价格允许的且包含它的单元,或者直到你用完列表为止。 当只剩下几件物品时,你需要将它们扔到最后一个篮子里。

另一个值得一提的问题是:这不是一种特别有效的方法。 从列表中删除元素、极其多的重复表达式还有一些其它的问题使得这不太适合解决这种大数据重新分配问题。 尽管如此,它仍然在我的老机器上运行。

如果你觉得我在这段代码中使用 while 循环并改变其中的数据感到不舒服,你可能希望我让它更有用一些。 我想不出一种方法不使用 Python 中的 map 和 reduce 函数,并结合随机选择的单元进行重新打包。 你可以吗?

在下一篇文章中,我将使用 Java 重新执行此操作,以了解 Groovy 和 Python 的工作量减少了多少,未来的文章将介绍 Julia 和 Go。


via: https://opensource.com/article/20/9/solve-problem-python

作者:Chris Hermansen 选题:lujun9972 译者:zepoch 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

使用这个强大的开源工具可以在命令行中纠正音乐标签错误。

很久以来我就将 CD 翻录到电脑。在此期间,我用过几种不同的翻录工具,观察到每种工具在标记上似乎有不同的做法,特别是在保存哪些音乐元数据上。所谓“观察”,我是指音乐播放器似乎按照有趣的顺序对专辑进行排序,它们将一个目录中的曲目分为两张专辑,或者产生了其他令人沮丧的烦恼。

我还看到有些标签非常不明确,许多音乐播放器和标签编辑器没有显示它们。即使这样,在某些极端情况下,它们仍可以使用这些标签来分类或显示音乐,例如播放器将所有包含 XYZ 标签的音乐文件与不包含该标签的所有文件分离到不同的专辑中。

那么,如果标记应用和音乐播放器没有显示“奇怪”的标记,但是它们受到了某种影响,你该怎么办?

Metaflac 来拯救!

我一直想要熟悉 metaflac,它是一款开源命令行 FLAC 文件元数据编辑器,这是我选择的开源音乐文件格式。并不是说 EasyTAG 这样出色的标签编辑软件有什么问题,但我想起“如果你手上有个锤子……”这句老话(LCTT 译注:指如果你手上有个锤子,那么所有的东西看起来都像钉子。意指人们惯于用熟悉的方式解决问题,而不管合不合适)。另外,从实际的角度来看,带有 ArmbianMPD 的小型专用服务器,音乐存储在本地、运行精简的仅限音乐的无头环境就可以满足我的家庭和办公室的立体音乐的需求,因此命令行元数据管理工具将非常有用。

下面的截图显示了我的长期翻录过程中产生的典型问题:Putumayo 的哥伦比亚音乐汇编显示为两张单独的专辑,一张包含单首曲目,另一张包含其余 11 首:

 title=

我使用 metaflac 为目录中包含这些曲目的所有 FLAC 文件生成了所有标签的列表:

rm -f tags.txt
for f in *.flac; do
        echo $f &gt;&gt; tags.txt
        metaflac --export-tags-to=tags.tmp "$f"
        cat tags.tmp &gt;&gt; tags.txt
        rm tags.tmp
done

我将其保存为可执行的 shell 脚本(请参阅我的同事 David Both 关于 Bash shell 脚本的精彩系列专栏文章,特别是关于循环这章)。基本上,我在这做的是创建一个文件 tags.txt,包含文件名(echo 命令),后面是它的所有标签,然后是下一个文件名,依此类推。这是结果的前几行:

A Guapi.flac
TITLE=A Guapi
ARTIST=Grupo Bahia
ALBUMARTIST=Various Artists
ALBUM=Putumayo Presents: Colombia
DATE=2001
TRACKTOTAL=12
GENRE=Latin Salsa
MUSICBRAINZ_ALBUMARTISTID=89ad4ac3-39f7-470e-963a-56509c546377
MUSICBRAINZ_ALBUMID=6e096386-1655-4781-967d-f4e32defb0a3
MUSICBRAINZ_ARTISTID=2993268d-feb6-4759-b497-a3ef76936671
DISCID=900a920c
ARTISTSORT=Grupo Bahia
MUSICBRAINZ_DISCID=RwEPU0UpVVR9iMP_nJexZjc_JCc-
COMPILATION=1
MUSICBRAINZ_TRACKID=8a067685-8707-48ff-9040-6a4df4d5b0ff
ALBUMARTISTSORT=50 de Joselito, Los
Cumbia Del Caribe.flac

经过一番调查,结果发现我同时翻录了很多 Putumayo CD,并且当时我所使用的所有软件似乎给除了一个之外的所有文件加上了 MUSICBRAINZ_* 标签。(是 bug 么?大概吧。我在六张专辑中都看到了。)此外,关于有时不寻常的排序,我注意到,ALBUMARTISTSORT 标签将西班牙语标题 “Los” 移到了标题的最后面(逗号之后)。

我使用了一个简单的 awk 脚本来列出 tags.txt 中报告的所有标签:

awk -F= 'index($0,"=") > 0 {print $1}' tags.txt | sort -u

这会使用 = 作为字段分隔符将所有行拆分为字段,并打印包含等号的行的第一个字段。结果通过使用 sort 及其 -u 标志来传递,从而消除了输出中的所有重复项(请参阅我的同事 Seth Kenlon 的关于 sort 程序的文章)。对于这个 tags.txt 文件,输出为:

ALBUM
ALBUMARTIST
ALBUMARTISTSORT
ARTIST
ARTISTSORT
COMPILATION
DATE
DISCID
GENRE
MUSICBRAINZ_ALBUMARTISTID
MUSICBRAINZ_ALBUMID
MUSICBRAINZ_ARTISTID
MUSICBRAINZ_DISCID
MUSICBRAINZ_TRACKID
TITLE
TRACKTOTAL

研究一会后,我发现 MUSICBRAINZ_* 标签出现在除了一个 FLAC 文件之外的所有文件上,因此我使用 metaflac 命令删除了这些标签:

for f in *.flac; do metaflac --remove-tag MUSICBRAINZ_ALBUMARTISTID "$f"; done
for f in *.flac; do metaflac --remove-tag MUSICBRAINZ_ALBUMID "$f"; done
for f in *.flac; do metaflac --remove-tag MUSICBRAINZ_ARTISTID "$f"; done
for f in *.flac; do metaflac --remove-tag MUSICBRAINZ_DISCID "$f"; done
for f in *.flac; do metaflac --remove-tag MUSICBRAINZ_TRACKID "$f"; done

完成后,我可以使用音乐播放器重建 MPD 数据库。结果如下:

 title=

完成了,12 首曲目出现在了一张专辑中。

太好了,我很喜欢 metaflac。我希望我会更频繁地使用它,因为我会试图去纠正最后一些我弄乱的音乐收藏标签。强烈推荐!

关于音乐

我花了几个晚上在 CBC 音乐(CBC 是加拿大的公共广播公司)上收听 Odario Williams 的节目 After Dark。感谢 Odario,我听到了让我非常享受的 Kevin Fox 的 Songs for Cello and Voice。在这里,他演唱了 Eurythmics 的歌曲 “Sweet Dreams(Are Made of This)”。

我购买了这张 CD,现在它在我的音乐服务器上,还有组织正确的标签!


via: https://opensource.com/article/19/11/metaflac-fix-music-tags

作者:Chris Hermansen 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

数组是一种有用的数据类型,用于管理在连续内存位置中建模最好的集合元素。下面是如何有效地使用它们。

 title=

有使用 C 或者 FORTRAN 语言编程经验的人会对数组的概念很熟悉。它们基本上是一个连续的内存块,其中每个位置都是某种数据类型:整型、浮点型或者诸如此类的数据类型。

Java 的情况与此类似,但是有一些额外的问题。

一个数组的示例

让我们在 Java 中创建一个长度为 10 的整型数组:

int[] ia = new int[10];

上面的代码片段会发生什么?从左到右依次是:

  1. 最左边的 int[] 将变量的类型声明为 int 数组(由 [] 表示)。
  2. 它的右边是变量的名称,当前为 ia
  3. 接下来,= 告诉我们,左侧定义的变量赋值为右侧的内容。
  4. = 的右侧,我们看到了 new,它在 Java 中表示一个对象正在被初始化中,这意味着已为其分配存储空间并调用了其构造函数(请参见此处以获取更多信息)。
  5. 然后,我们看到 int[10],它告诉我们正在初始化的这个对象是包含 10 个整型的数组。

因为 Java 是强类型的,所以变量 ia 的类型必须跟 = 右侧表达式的类型兼容。

初始化示例数组

让我们把这个简单的数组放在一段代码中,并尝试运行一下。将以下内容保存到一个名为 Test1.java 的文件中,使用 javac 编译,使用 java 运行(当然是在终端中):

import java.lang.*;

public class Test1 {

    public static void main(String[] args) {
        int[] ia = new int[10];                              // 见下文注 1
        System.out.println("ia is " + ia.getClass());        // 见下文注 2
        for (int i = 0; i < ia.length; i++)                  // 见下文注 3
            System.out.println("ia[" + i + "] = " + ia[i]);  // 见下文注 4
    }

}

让我们来看看最重要的部分。

  1. 我们声明和初始化了长度为 10 的整型数组,即 ia,这显而易见。
  2. 在下面的行中,我们看到表达式 ia.getClass()。没错,ia 是属于一个对象,这行代码将告诉我们是哪个类。
  3. 在紧接的下一行中,我们看到了一个循环 for (int i = 0; i < ia.length; i++),它定义了一个循环索引变量 i,该变量遍历了从 0 到比 ia.length 小 1 的序列,这个表达式告诉我们在数组 ia 中定义了多少个元素。
  4. 接下来,循环体打印出 ia 的每个元素的值。

当这个程序编译和运行时,它产生以下结果:

me@mydesktop:~/Java$ javac Test1.java
me@mydesktop:~/Java$ java Test1
ia is class [I
ia[0] = 0
ia[1] = 0
ia[2] = 0
ia[3] = 0
ia[4] = 0
ia[5] = 0
ia[6] = 0
ia[7] = 0
ia[8] = 0
ia[9] = 0
me@mydesktop:~/Java$

ia.getClass() 的输出的字符串表示形式是 [I,它是“整数数组”的简写。与 C 语言类似,Java 数组以第 0 个元素开始,扩展到第 <数组大小> - 1 个元素。如上所见,我们可以看到数组 ia 的每个元素都(似乎由数组构造函数)设置为零。

所以,就这些吗?声明类型,使用适当的初始化器,就完成了吗?

好吧,并没有。在 Java 中有许多其它方法来初始化数组。

为什么我要初始化一个数组,有其它方式吗?

像所有好的问题一样,这个问题的答案是“视情况而定”。在这种情况下,答案取决于初始化后我们希望对数组做什么。

在某些情况下,数组自然会作为一种累加器出现。例如,假设我们正在编程实现计算小型办公室中一组电话分机接收和拨打的电话数量。一共有 8 个分机,编号为 1 到 8,加上话务员的分机,编号为 0。 因此,我们可以声明两个数组:

int[] callsMade;
int[] callsReceived;

然后,每当我们开始一个新的累计呼叫统计数据的周期时,我们就将每个数组初始化为:

callsMade = new int[9];
callsReceived = new int[9];

在每个累计通话统计数据的最后阶段,我们可以打印出统计数据。粗略地说,我们可能会看到:

import java.lang.*;
import java.io.*;

public class Test2 {

    public static void main(String[] args) {

        int[] callsMade;
        int[] callsReceived;

        // 初始化呼叫计数器

        callsMade = new int[9];
        callsReceived = new int[9];

        // 处理呼叫……
        //   分机拨打电话:callsMade[ext]++
        //   分机接听电话:callsReceived[ext]++

        // 汇总通话统计

        System.out.printf("%3s%25s%25s\n", "ext", " calls made",
                "calls received");
        for (int ext = 0; ext < callsMade.length; ext++) {
            System.out.printf("%3d%25d%25d\n", ext,
                    callsMade[ext], callsReceived[ext]);
        }

    }

}

这会产生这样的输出:

me@mydesktop:~/Java$ javac Test2.java
me@mydesktop:~/Java$ java Test2
ext               calls made           calls received
  0                        0                        0
  1                        0                        0
  2                        0                        0
  3                        0                        0
  4                        0                        0
  5                        0                        0
  6                        0                        0
  7                        0                        0
  8                        0                        0
me@mydesktop:~/Java$

看来这一天呼叫中心不是很忙。

在上面的累加器示例中,我们看到由数组初始化程序设置的零起始值可以满足我们的需求。但是在其它情况下,这个起始值可能不是正确的选择。

例如,在某些几何计算中,我们可能需要将二维数组初始化为单位矩阵(除沿主对角线———左上角到右下角——以外所有全是零)。我们可以选择这样做:

double[][] m = new double[3][3];
for (int d = 0; d < 3; d++) {
    m[d][d] = 1.0;
}

在这种情况下,我们依靠数组初始化器 new double[3][3] 将数组设置为零,然后使用循环将主对角线上的元素设置为 1。在这种简单情况下,我们可以使用 Java 提供的快捷方式:

double[][] m = {
        {1.0, 0.0, 0.0},
        {0.0, 1.0, 0.0},
        {0.0, 0.0, 1.0}};

这种可视结构特别适用于这种应用程序,在这种应用程序中,它便于复查数组的实际布局。但是在这种情况下,行数和列数只在运行时确定时,我们可能会看到这样的东西:

int nrc;
// 一些代码确定行数和列数 = nrc
double[][] m = new double[nrc][nrc];
for (int d = 0; d < nrc; d++) {
    m[d][d] = 1.0;
}

值得一提的是,Java 中的二维数组实际上是数组的数组,没有什么能阻止无畏的程序员让这些第二层数组中的每个数组的长度都不同。也就是说,下面这样的事情是完全合法的:

int [][] differentLengthRows = {
     {1, 2, 3, 4, 5},
     {6, 7, 8, 9},
     {10, 11, 12},
     {13, 14},
     {15}};

在涉及不规则形状矩阵的各种线性代数应用中,可以应用这种类型的结构(有关更多信息,请参见此 Wikipedia 文章)。除此之外,既然我们了解到二维数组实际上是数组的数组,那么以下内容也就不足为奇了:

differentLengthRows.length

可以告诉我们二维数组 differentLengthRows 的行数,并且:

differentLengthRows[i].length

告诉我们 differentLengthRowsi 行的列数。

深入理解数组

考虑到在运行时确定数组大小的想法,我们看到数组在实例化之前仍需要我们知道该大小。但是,如果在处理完所有数据之前我们不知道大小怎么办?这是否意味着我们必须先处理一次以找出数组的大小,然后再次处理?这可能很难做到,尤其是如果我们只有一次机会使用数据时。

Java 集合框架很好地解决了这个问题。提供的其中一项是 ArrayList 类,它类似于数组,但可以动态扩展。为了演示 ArrayList 的工作原理,让我们创建一个 ArrayList 对象并将其初始化为前 20 个斐波那契数字

import java.lang.*;
import java.util.*;

public class Test3 {

    public static void main(String[] args) {

        ArrayList<Integer> fibos = new ArrayList<Integer>();

        fibos.add(0);
        fibos.add(1);
        for (int i = 2; i < 20; i++) {
            fibos.add(fibos.get(i - 1) + fibos.get(i - 2));
        }

        for (int i = 0; i < fibos.size(); i++) {
            System.out.println("fibonacci " + i + " = " + fibos.get(i));
        }

    }
}

上面的代码中,我们看到:

  • 用于存储多个 IntegerArrayList 的声明和实例化。
  • 使用 add() 附加到 ArrayList 实例。
  • 使用 get() 通过索引号检索元素。
  • 使用 size() 来确定 ArrayList 实例中已经有多少个元素。

这里没有展示 put() 方法,它的作用是将一个值放在给定的索引号上。

该程序的输出为:

fibonacci 0 = 0
fibonacci 1 = 1
fibonacci 2 = 1
fibonacci 3 = 2
fibonacci 4 = 3
fibonacci 5 = 5
fibonacci 6 = 8
fibonacci 7 = 13
fibonacci 8 = 21
fibonacci 9 = 34
fibonacci 10 = 55
fibonacci 11 = 89
fibonacci 12 = 144
fibonacci 13 = 233
fibonacci 14 = 377
fibonacci 15 = 610
fibonacci 16 = 987
fibonacci 17 = 1597
fibonacci 18 = 2584
fibonacci 19 = 4181

ArrayList 实例也可以通过其它方式初始化。例如,可以给 ArrayList 构造器提供一个数组,或者在编译过程中知道初始元素时也可以使用 List.of()array.aslist() 方法。我发现自己并不经常使用这些方式,因为我对 ArrayList 的主要用途是当我只想读取一次数据时。

此外,对于那些喜欢在加载数据后使用数组的人,可以使用 ArrayListtoArray() 方法将其实例转换为数组;或者,在初始化 ArrayList 实例之后,返回到当前数组本身。

Java 集合框架提供了另一种类似数组的数据结构,称为 Map(映射)。我所说的“类似数组”是指 Map 定义了一个对象集合,它的值可以通过一个键来设置或检索,但与数组(或 ArrayList)不同,这个键不需要是整型数;它可以是 String 或任何其它复杂对象。

例如,我们可以创建一个 Map,其键为 String,其值为 Integer 类型,如下:

Map<String, Integer> stoi = new Map<String, Integer>();

然后我们可以对这个 Map 进行如下初始化:

stoi.set("one",1);
stoi.set("two",2);
stoi.set("three",3);

等类似操作。稍后,当我们想要知道 "three" 的数值时,我们可以通过下面的方式将其检索出来:

stoi.get("three");

在我的认知中,Map 对于将第三方数据集中出现的字符串转换为我的数据集中的一致代码值非常有用。作为数据转换管道的一部分,我经常会构建一个小型的独立程序,用作在处理数据之前清理数据;为此,我几乎总是会使用一个或多个 Map

值得一提的是,ArrayListArrayListMapMap 是很可能的,有时也是合理的。例如,假设我们在看树,我们对按树种和年龄范围累计树的数目感兴趣。假设年龄范围定义是一组字符串值(“young”、“mid”、“mature” 和 “old”),物种是 “Douglas fir”、“western red cedar” 等字符串值,那么我们可以将这个 Map 中的 Map 定义为:

Map<String, Map<String, Integer>> counter = new Map<String, Map<String, Integer>>();

这里需要注意的一件事是,以上内容仅为 Map创建存储。因此,我们的累加代码可能类似于:

// 假设我们已经知道了物种和年龄范围
if (!counter.containsKey(species)) {
    counter.put(species,new Map<String, Integer>());
}
if (!counter.get(species).containsKey(ageRange)) {
    counter.get(species).put(ageRange,0);
}

此时,我们可以这样开始累加:

counter.get(species).put(ageRange, counter.get(species).get(ageRange) + 1);

最后,值得一提的是(Java 8 中的新特性)Streams 还可以用来初始化数组、ArrayList 实例和 Map 实例。关于此特性的详细讨论可以在此处此处中找到。


via: https://opensource.com/article/19/10/initializing-arrays-java

作者:Chris Hermansen 选题:lujun9972 译者:laingke 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

根据你的工作需要,可能有比 Java 更好的语言,但是我还没有看到任何能把我拉走的语言。

我记得我是从 1997 年开始使用 Java 的,就在 Java 1.1 刚刚发布不久之后。从那时起,总的来说,我非常喜欢用 Java 编程;虽然我得承认,这些日子我经常像在 Java 中编写“严肃的代码”一样编写 Groovy 脚本。

来自 FORTRANPL/1Pascal) 以及最后的 C 语言) 背景,我发现了许多让我喜欢 Java 的东西。Java 是我面向对象编程的第一次重要实践经验。到那时,我已经编程了大约 20 年,而且可以说我对什么重要、什么不重要有了一些看法。

调试是一个关键的语言特性

我真的很讨厌浪费时间追踪由我的代码不小心迭代到数组末尾而导致的模糊错误,特别是在 IBM 大型机上的 FORTRAN 编程时代。另一个不时出现的隐晦问题是调用一个子程序时,该子程序带有一个四字节整数参数,而预期有两个字节;在小端架构上,这通常是一个良性的错误,但在大端机器上,前两个字节的值通常并不总是为零。

在那种批处理环境中进行调试也非常不便,通过核心转储或插入打印语句进行调试,这些语句本身会移动错误的位置甚至使它们消失。

所以我使用 Pascal 的早期体验,先是在 MTS 上,然后是在 IBM OS/VS1 上使用相同的 MTS 编译器,让我的生活变得更加轻松。Pascal 的强类型和静态类型是取得这种胜利的重要组成部分,我使用的每个 Pascal 编译器都会在数组的边界和范围上插入运行时检查,因此错误可以在发生时检测到。当我们在 20 世纪 80 年代早期将大部分工作转移到 Unix 系统时,移植 Pascal 代码是一项简单的任务。

适量的语法

但是对于我所喜欢的 Pascal 来说,我的代码很冗长,而且语法似乎要比代码还要多;例如,使用:

if ... then begin ... end else ... end

而不是 C 或类似语言中的:

if (...) { ... } else { ... }

另外,有些事情在 Pascal 中很难完成,在 C 中更容易。但是,当我开始越来越多地使用 C 时,我发现自己遇到了我曾经在 FORTRAN 中遇到的同样类型的错误,例如,超出数组边界。在原始的错误点未检测到数组结束,而仅在程序执行后期才会检测到它们的不利影响。幸运的是,我不再生活在那种批处理环境中,并且手头有很好的调试工具。不过,C 对于我来说有点太灵活了。

当我遇到 awk 时,我发现它与 C 相比又是另外一种样子。那时,我的很多工作都涉及转换字段数据并创建报告。我发现用 awk 加上其他 Unix 命令行工具,如 sortsedcutjoinpastecomm 等等,可以做到事情令人吃惊。从本质上讲,这些工具给了我一个像是基于文本文件的关系数据库管理器,这种文本文件具有列式结构,是我们很多字段数据的保存方式。或者,即便不是这种格式,大部分时候也可以从关系数据库或某种二进制格式导出到列式结构中。

awk 支持的字符串处理、正则表达式关联数组,以及 awk 的基本特性(它实际上是一个数据转换管道),非常符合我的需求。当面对二进制数据文件、复杂的数据结构和关键性能需求时,我仍然会转回到 C;但随着我越来越多地使用 awk,我发现 C 的非常基础的字符串支持越来越令人沮丧。随着时间的推移,更多的时候我只会在必须时才使用 C,并且在其余的时候里大量使用 awk

Java 的抽象层级合适

然后是 Java。它看起来相当不错 —— 相对简洁的语法,让人联想到 C,或者这种相似性至少要比 Pascal 或其他任何早期的语言更为明显。它是强类型的,因此很多编程错误会在编译时被捕获。它似乎并不需要过多的面向对象的知识就能起步,这是一件好事,因为我当时对 OOP 设计模式毫不熟悉。但即使在刚刚开始,我也喜欢它的简化继承模型背后的思想。(Java 允许使用提供的接口进行单继承,以在某种程度上丰富范例。)

它似乎带有丰富的功能库(即“自备电池”的概念),在适当的水平上直接满足了我的需求。最后,我发现自己很快就会想到将数据和行为在对象中组合在一起的想法。这似乎是明确控制数据之间交互的好方法 —— 比大量的参数列表或对全局变量的不受控制的访问要好得多。

从那以后,Java 在我的编程工具箱中成为了 Helvetic 军刀。我仍然偶尔会在 awk 中编写程序,或者使用 Linux 命令行实用程序(如 cutsortsed),因为它们显然是解决手头问题的直接方法。我怀疑过去 20 年我可能没写过 50 行的 C 语言代码;Java 完全满足了我的需求。

此外,Java 一直在不断改进。首先,它变得更加高效。并且它添加了一些非常有用的功能,例如可以用 try 来测试资源,它可以很好地清理在文件 I/O 期间冗长而有点混乱的错误处理代码;或 lambda,它提供了声明函数并将其作为参数传递的能力,而旧方法需要创建类或接口来“托管”这些函数;或,它在函数中封装了迭代行为,可以创建以链式函数调用形式实现的高效数据转换管道。

Java 越来越好

许多语言设计者研究了从根本上改善 Java 体验的方法。对我来说,其中大部分没有引起我的太多兴趣;再次,这更多地反映了我的典型工作流程,并且(更多地)减少了这些语言带来的功能。但其中一个演化步骤已经成为我的编程工具中不可或缺的一部分:Groovy。当我遇到一个小问题,需要一个简单的解决方案时,Groovy 已经成为了我的首选。而且,它与 Java 高度兼容。对我来说,Groovy 填补了 Python 为许多其他人所提供的相同用处 —— 它紧凑、DRY(不要重复自己)和具有表达性(列表和词典有完整的语言支持)。我还使用了 Grails,它使用 Groovy 为非常高性能和有用的 Java Web 应用程序提供简化的 Web 框架。

Java 仍然开源吗?

最近,对 OpenJDK 越来越多的支持进一步提高了我对 Java 的舒适度。许多公司以各种方式支持 OpenJDK,包括 AdoptOpenJDK、Amazon 和 Red Hat。在我的一个更大、更长期的项目中,我们使用 AdoptOpenJDK 来在几个桌面平台上生成自定义的运行时环境

有没有比 Java 更好的语言?我确信有,这取决于你的工作需要。但我一直对 Java 非常满意,我还没有遇到任何可能会让我失望的东西。


via: https://opensource.com/article/19/9/why-i-use-java

作者:Chris Hermansen 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这篇文章是关于如何在使用 cp 命令进行备份以及同步时提高效率。

去年七月,我写了一篇关于 cp 命令的两种绝佳用法的文章:备份一个文件,以及同步一个文件夹的备份。

虽然这些工具确实很好用,但同时,输入这些命令太过于累赘了。为了解决这个问题,我在我的 Bash 启动文件里创建了一些 Bash 快捷方式。现在,我想把这些捷径分享给你们,以便于你们在需要的时候可以拿来用,或者是给那些还不知道怎么使用 Bash 的别名以及函数的用户提供一些思路。

使用 Bash 别名来更新一个文件夹的副本

如果要使用 cp 来更新一个文件夹的副本,通常会使用到的命令是:

cp -r -u -v SOURCE-FOLDER DESTINATION-DIRECTORY

其中 -r 代表“向下递归访问文件夹中的所有文件”,-u 代表“更新目标”,-v 代表“详细模式”,SOURCE-FOLDER 是包含最新文件的文件夹的名称,DESTINATION-DIRECTORY 是包含必须同步的SOURCE-FOLDER 副本的目录。

因为我经常使用 cp 命令来复制文件夹,我会很自然地想起使用 -r 选项。也许再想地更深入一些,我还可以想起用 -v 选项,如果再想得再深一层,我会想起用选项 -u(不知道这个选项是代表“更新”还是“同步”还是一些什么其它的)。

或者,还可以使用Bash 的别名功能来将 cp 命令以及其后的选项转换成一个更容易记忆的单词,就像这样:

alias sync='cp -r -u -v'

如果我将其保存在我的主目录中的 .bash_aliases 文件中,然后启动一个新的终端会话,我可以使用该别名了,例如:

sync Pictures /media/me/4388-E5FE

可以将我的主目录中的图片文件夹与我的 USB 驱动器中的相同版本同步。

不清楚 sync 是否已经定义了?你可以在终端里输入 alias 这个单词来列出所有正在使用的命令别名。

喜欢吗?想要现在就立即使用吗?那就现在打开终端,输入:

echo "alias sync='cp -r -u -v'" >> ~/.bash_aliases

然后启动一个新的终端窗口并在命令提示符下键入 alias。你应该看到这样的东西:

me@mymachine~$ alias

alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias gvm='sdk'
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
alias sync='cp -r -u -v'
me@mymachine:~$

这里你能看到 sync 已经定义了。

使用 Bash 函数来为备份编号

若要使用 cp 来备份一个文件,通常使用的命令是:

cp --force --backup=numbered WORKING-FILE BACKED-UP-FILE

其中 --force 代表“强制制作副本”,--backup= numbered 代表“使用数字表示备份的生成”,WORKING-FILE 是我们希望保留的当前文件,BACKED-UP-FILEWORKING-FILE 的名称相同,并附加生成信息。

我们不仅需要记得所有 cp 的选项,我们还需要记得去重复输入 WORKING-FILE 的名字。但当Bash 的函数功能已经可以帮我们做这一切,为什么我们还要不断地重复这个过程呢?就像这样:

再一次提醒,你可将下列内容保存入你在家目录下的 .bash_aliases 文件里:

function backup {
    if [ $# -ne 1 ]; then
        echo "Usage: $0 filename"
    elif [ -f $1 ] ; then
        echo "cp --force --backup=numbered $1 $1"
        cp --force --backup=numbered $1 $1
    else
        echo "$0: $1 is not a file"
    fi
}

我将此函数称之为 backup,因为我的系统上没有任何其他名为 backup 的命令,但你可以选择适合的任何名称。

第一个 if 语句是用于检查是否提供有且只有一个参数,否则,它会用 echo 命令来打印出正确的用法。

elif 语句是用于检查提供的参数所指向的是一个文件,如果是的话,它会用第二个 echo 命令来打印所需的 cp 的命令(所有的选项都是用全称来表示)并且执行它。

如果所提供的参数不是一个文件,文件中的第三个 echo 用于打印错误信息。

在我的家目录下,如果我执行 backup 这个命令,我可以发现目录下多了一个文件名为checkCounts.sql.~1~ 的文件,如果我再执行一次,便又多了另一个名为 checkCounts.sql.~2~ 的文件。

成功了!就像所想的一样,我可以继续编辑 checkCounts.sql,但如果我可以经常地用这个命令来为文件制作快照的话,我可以在我遇到问题的时候回退到最近的版本。

也许在未来的某个时间,使用 git 作为版本控制系统会是一个好主意。但像上文所介绍的 backup 这个简单而又好用的工具,是你在需要使用快照的功能时却还未准备好使用 git 的最好工具。

结论

在我的上一篇文章里,我保证我会通过使用脚本,shell 里的函数以及别名功能来简化一些机械性的动作来提高生产效率。

在这篇文章里,我已经展示了如何在使用 cp 命令同步或者备份文件时运用 shell 函数以及别名功能来简化操作。如果你想要了解更多,可以读一下这两篇文章:怎样通过使用命令别名功能来减少敲击键盘的次数 以及由我的同事 Greg 和 Seth 写的 Shell 编程:shift 方法和自定义函数介绍


via: https://opensource.com/article/18/1/two-great-uses-cp-command-update

作者:Chris Hermansen 译者:zyk2290 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你可以依靠这些应用来满足你的生产力、沟通和娱乐需求。

像世界上大多数人一样,我的手似乎就没有离开过手机。多亏了我从 Google Play 和 F-Droid 安装的开源移动应用程序,让我的 Android 设备好像提供了无限的沟通、生产力和娱乐服务一样。

在我的手机上的许多开源应用程序中,当想听音乐、与朋友/家人和同事联系、或者在旅途中完成工作时,以下五个是我一直使用的。

MPDroid

一个音乐播放器进程 (MPD)的 Android 控制器。

 title=

MPD 是将音乐从小型音乐服务器电脑传输到大型的黑色立体声音箱的好方法。它直连 ALSA,因此可以通过 ALSA 硬件接口与数模转换器(DAC)对话,它可以通过我的网络进行控制——但是用什么东西控制呢?好吧,事实证明 MPDroid 是一个很棒的 MPD 控制器。它可以管理我的音乐数据库,显示专辑封面,处理播放列表,并支持互联网广播。而且它是开源的,所以如果某些东西不好用的话……

MPDroid 可在 Google PlayF-Droid 上找到。

RadioDroid

一台能单独使用及与 Chromecast 搭配使用的 Android 网络收音机。

 title=

RadioDroid 是一个网络收音机,而 MPDroid 则管理我音乐的数据库;从本质上讲,RadioDroid 是 Internet-Radio.com 的一个前端。此外,通过将耳机插入 Android 设备,通过耳机插孔或 USB 将 Android 设备直接连接到立体声系统,或通过兼容设备使用其 Chromecast 功能,可以享受 RadioDroid。这是一个查看芬兰天气情况,听取排名前 40 的西班牙语音乐,或收到到最新新闻消息的好方法。

RadioDroid 可在 Google PlayF-Droid 上找到。

Signal

一个支持 Android、iOS,还有桌面系统的安全即时消息客户端。

 title=

如果你喜欢 WhatsApp,但是因为它与 Facebook 日益密切的关系而感到困扰,那么 Signal 应该是你的下一个产品。Signal 的唯一问题是说服你的朋友们最好用 Signal 取代 WhatsApp。但除此之外,它有一个与 WhatsApp 类似的界面;很棒的语音和视频通话;很好的加密;恰到好处的匿名;并且它受到了一个不打算通过使用软件来获利的基金会的支持。为什么不喜欢它呢?

Signal 可用于 AndroidiOS桌面

ConnectBot

Android SSH 客户端。

 title=

有时我离电脑很远,但我需要登录服务器才能办事。ConnectBot 是将 SSH 会话搬到手机上的绝佳解决方案。

ConnectBot 可在 Google Play 上找到。

Termux

有多种熟悉的功能的安卓终端模拟器。

 title=

你是否需要在手机上运行 awk 脚本?Termux 是个解决方案。如果你需要做终端类的工作,而且你不想一直保持与远程计算机的 SSH 连接,请使用 ConnectBot 将文件放到手机上,然后退出会话,在 Termux 中执行你的操作,用 ConnectBot 发回结果。

Termux 可在 Google PlayF-Droid 上找到。


你最喜欢用于工作或娱乐的开源移动应用是什么呢?请在评论中分享它们。


via: https://opensource.com/article/19/4/mobile-apps

作者:Chris Hermansen 选题:lujun9972 译者:fuzheng1998 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出